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: 124/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 | Contexts | |
---|---|---|
QA‑1 | address wallet in USDS.mintTo() can be address(collateralAndLiquidity) | 1 |
QA‑2 | ecrecover may return empty address | 1 |
QA‑3 | forceApprove should be used instead of approve | 4 |
QA‑4 | The Contract Should approve(0) First | 8 |
QA‑5 | address shouldn't be hard-coded | 1 |
QA‑6 | Change contract's mint model | 1 |
QA‑7 | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 2 |
QA‑8 | A function which defines named returns in it's declaration doesn't need to use return | 42 |
QA‑9 | Use delete to Clear Variables | 6 |
QA‑10 | Incorrect withdraw declaration | 2 |
QA‑11 | Off-by-one timestamp error | 8 |
QA‑12 | Redundant Cast | 1 |
QA‑13 | Indexed strings should be removed | 1 |
Total: 78 contexts over 13 issues
address wallet
in USDS.mintTo()
can be address(collateralAndLiquidity)
Since the only caller can be address(collateralAndLiquidity)
according to require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" );
We can see that in CollateralAndLiquidity.borrowUSDS()
calls for usds.mintTo( msg.sender, amountBorrowed );
As a result, we can remove address wallet
and simply call to _mint(address(collateralAndLiquidity), amount);
File: USDS.sol 40: function mintTo( address wallet, uint256 amount ) external { require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" ); require( amount > 0, "Cannot mint zero USDS" ); _mint( wallet, amount ); emit USDSMinted(wallet, amount); }
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/USDS.sol#L40
ecrecover
may return empty addressThere is a common issue that ecrecover returns empty (0x0) address when the signature is invalid. function _verifySignature
should check that before returning the result of ecrecover.
Should revert when recoveredAddress
is zero.
File: SigningTools.sol 26: address recoveredAddress = ecrecover(messageHash, v, r, s);
https://github.com/code-423n4/2024-01-salty/tree/main/src/SigningTools.sol#L26
forceApprove
should be used instead of approve
In order to prevent front running, forceApprove
should be used in place of approve
where possible
File: Liquidity.sol 58: tokenA.approve( address(pools), swapAmountA );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L58
File: Liquidity.sol 68: tokenB.approve( address(pools), swapAmountB );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L68
File: Liquidity.sol 96: tokenA.approve( address(pools), maxAmountA );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L96
File: Liquidity.sol 97: tokenB.approve( address(pools), maxAmountB );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L97
approve(0)
FirstSome tokens (like USDT L199) do not work when changing the allowance from an existing non-zero allowance value. They must first be approved by zero and then the actual allowance must be approved.
File: Liquidity.sol 58: tokenA.approve( address(pools), swapAmountA );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L58
File: Liquidity.sol 68: tokenB.approve( address(pools), swapAmountB );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L68
File: Liquidity.sol 96: tokenA.approve( address(pools), maxAmountA );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L96
File: Liquidity.sol 97: tokenB.approve( address(pools), maxAmountB );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L97
</details>Approve with a zero amount first before setting the actual amount.
address
shouldn't be hard-codedIt is often better to declare address
es as immutable
, and assign them via constructor arguments. This allows the code to remain the same across deployments on different networks, and avoids recompilation when addresses need to change.
File: SigningTools.sol 7: address constant public EXPECTED_SIGNER = 0x1234519DCA2ef23207E1CA7fd70b96f281893bAa;
https://github.com/code-423n4/2024-01-salty/tree/main/src/SigningTools.sol#L7
mint
modelInstead of having a fixed total supply of the contract's token and minting the entire total supply to a single address, use the minting model at the time of transaction, so total supply will only be as much as the token in use and there will be no tokens minted in a single address.
One advantage of using the constant for total supply in the affected contracts is that it allows you to specify the total supply of tokens in a single place, which can make it easier to maintain and understand the contract.
One disadvantage is that the total supply of tokens is hard-coded into the contract and cannot be changed later. This means that if the total supply needs to be changed for any reason (e.g., to issue more tokens or to burn existing tokens), the contract will need to be modified and redeployed. This can be time-consuming and costly.
Another potential disadvantage is that the use of a constant value for the total supply may make the contract less flexible. For example, if the total supply needs to be determined dynamically based on some external factor (e.g., the total amount of funds raised in a crowdfunding campaign), the contract will need to be modified to accommodate this.
Finally, using a constant value for the total supply may make it more difficult to implement certain features or behaviors that depend on the total supply, such as vesting or token burns. These features may require additional logic to be implemented in the contract, which could increase its complexity and gas cost.
File: Salt.sol 13: uint256 public constant INITIAL_SUPPLY = 100 * MILLION_ETHER ;
https://github.com/code-423n4/2024-01-salty/tree/main/src/Salt.sol#L13
While adherence to the check-effects-interaction pattern is commendable, the absence of a reentrancy guard in functions, especially where transfer hooks might be present, can expose the protocol users to risks of read-only reentrancies. Such reentrancy vulnerabilities can be exploited to execute malicious actions even without altering the contract state. Without a reentrancy guard, the only potential mitigation would be to blocklist the entire protocol - an extreme and disruptive measure. Therefore, incorporating a reentrancy guard into these functions is vital to bolster security, as it helps protect against both traditional reentrancy attacks and read-only reentrancies, ensuring robust and safe protocol operations.
File: DAO.sol ```solidity File: DAO.sol 360: function withdrawPOL( IERC20 tokenA, IERC20 tokenB, uint256 percentToLiquidate ) external { require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" ); bytes32 poolID = PoolUtils._poolID(tokenA, tokenB); uint256 liquidityHeld = collateralAndLiquidity.userShareForPool( address(this), poolID ); if ( liquidityHeld == 0 ) return; uint256 liquidityToWithdraw = (liquidityHeld * percentToLiquidate) / 100; (uint256 reclaimedA, uint256 reclaimedB) = collateralAndLiquidity.withdrawLiquidityAndClaim(tokenA, tokenB, liquidityToWithdraw, 0, 0, block.timestamp ); tokenA.safeTransfer( address(liquidizer), reclaimedA ); tokenB.safeTransfer( address(liquidizer), reclaimedB ); emit POLWithdrawn(tokenA, tokenB, reclaimedA, reclaimedB); }
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L360
File: Liquidity.sol 121: function _withdrawLiquidityAndClaim( IERC20 tokenA, IERC20 tokenB, uint256 liquidityToWithdraw, uint256 minReclaimedA, uint256 minReclaimedB ) internal returns (uint256 reclaimedA, uint256 reclaimedB) { bytes32 poolID = PoolUtils._poolID( tokenA, tokenB ); require( userShareForPool(msg.sender, poolID) >= liquidityToWithdraw, "Cannot withdraw more than existing user share" ); (reclaimedA, reclaimedB) = pools.removeLiquidity( tokenA, tokenB, liquidityToWithdraw, minReclaimedA, minReclaimedB, totalShares[poolID] ); tokenA.safeTransfer( msg.sender, reclaimedA ); tokenB.safeTransfer( msg.sender, reclaimedB ); _decreaseUserShare( msg.sender, poolID, liquidityToWithdraw, true ); emit LiquidityWithdrawn(msg.sender, address(tokenA), address(tokenB), reclaimedA, reclaimedB, liquidityToWithdraw); }
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L121
Remove the return statement once ensuring it is safe to do so
File: ArbitrageSearch.sol 37: return (wbtc, salt); 43: return (salt, wbtc); 48: return (wbtc, swapTokenOut); 53: return (swapTokenIn, wbtc); 57: return (swapTokenOut, swapTokenIn);
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L37
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L43
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L48
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L53
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L57
File: ArbitrageSearch.sol 76: return false; 88: return profitRightOfMidpoint > profitMidpoint;
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L76
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L88
File: ArbitrageSearch.sol 108: return 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L108
File: DAO.sol 302: return 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L302
File: Proposals.sol 126: return _possiblyCreateProposal( ballotName, ballotType, address1, 0, string1, description ); 158: return _possiblyCreateProposal( ballotName, BallotType.PARAMETER, address(0), parameterType, "", description ); 176: return ballotID; 190: return _possiblyCreateProposal( ballotName, BallotType.UNWHITELIST_TOKEN, address(token), 0, tokenIconURL, description );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L126
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L158
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L176
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L190
File: Proposals.sol 208: return _possiblyCreateProposal( ballotName, BallotType.SEND_SALT, wallet, amount, "", description );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L208
File: Proposals.sol 218: return _possiblyCreateProposal( ballotName, BallotType.CALL_CONTRACT, contractAddress, number, description, "" ); 227: return _possiblyCreateProposal( ballotName, BallotType.INCLUDE_COUNTRY, address(0), 0, country, description ); 236: return _possiblyCreateProposal( ballotName, BallotType.EXCLUDE_COUNTRY, address(0), 0, country, description ); 245: return _possiblyCreateProposal( ballotName, BallotType.SET_CONTRACT, newAddress, 0, "", description ); 254: return _possiblyCreateProposal( ballotName, BallotType.SET_WEBSITE_URL, address(0), 0, newWebsiteURL, description );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L218
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L227
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L236
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L245
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L254
File: Proposals.sol 299: return ballots[ballotID]; 305: return _lastUserVoteForBallot[ballotID][user]; 311: return _votesCastForBallot[ballotID][vote];
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L299
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L305
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L311
File: Proposals.sol 348: return votes[Vote.INCREASE] + votes[Vote.DECREASE] + votes[Vote.NO_CHANGE]; 350: return votes[Vote.YES] + votes[Vote.NO];
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L348
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L350
File: PoolMath.sol 173: return 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L173
File: PoolMath.sol 224: return (0, 0); 226: return (swapAmountA, swapAmountB);
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L224
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L226
File: Pools.sol 104: return ( maxAmount0, maxAmount1, (maxAmount0 + maxAmount1) );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L104
File: Pools.sol 305: return swapAmountIn; 311: return 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L305
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L311
File: Pools.sol 326: return 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L326
File: PoolStats.sol 68: return uint64(i); 71: return INVALID_POOL_ID;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L68
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L71
File: PoolStats.sol 145: return _arbitrageIndicies[poolID];
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L145
File: PoolUtils.sol 25: return keccak256(abi.encodePacked(address(tokenB), address(tokenA))); 27: return keccak256(abi.encodePacked(address(tokenA), address(tokenB)));
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L25
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L27
File: PoolUtils.sol 36: return (keccak256(abi.encodePacked(address(tokenB), address(tokenA))), true); 38: return (keccak256(abi.encodePacked(address(tokenA), address(tokenB))), false);
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L36
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L38
File: PoolUtils.sol 57: return (0, 0);
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L57
File: Liquidity.sol 75: return (zapAmountA, zapAmountB);
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L75
File: Liquidity.sol 150: return _depositLiquidityAndIncreaseShare(tokenA, tokenB, maxAmountA, maxAmountB, minLiquidityReceived, useZapping);
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L150
File: Liquidity.sol 161: return _withdrawLiquidityAndClaim(tokenA, tokenB, liquidityToWithdraw, minReclaimedA, minReclaimedB);
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Liquidity.sol#L161
</details>delete
to Clear Variablesdelete a
assigns the initial value for the type to a
. i.e. for integers it is equivalent to a = 0
, but it can also be used on arrays, where it assigns a dynamic array of length zero or a static array of the same length with all elements reset. For structs, it assigns a struct with all members reset. Similarly, it can also be used to set an address to zero address. It has no effect on whole mappings though (as the keys of mappings may be arbitrary and are generally unknown). However, individual keys and what they map to can be deleted: If a
is a mapping, then delete a[x]
will delete the value stored at x
.
The delete
key better conveys the intention and is also more idiomatic. Consider replacing assignments of zero with delete
statements.
File: PoolStats.sol 54: _arbitrageProfits[ poolIDs[i] ] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L54
File: CoreChainlinkFeed.sol 30: int256 price = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreChainlinkFeed.sol#L30
File: CollateralAndLiquidity.sol 184: usdsBorrowedByUsers[wallet] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L184
File: Liquidizer.sol 113: usdsThatShouldBeBurned = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L113
File: StakingRewards.sol 151: claimableRewards = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L151
File: StakingRewards.sol 301: cooldowns[i] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L301
</details>In Solidity, it's essential for clarity and interoperability to correctly specify return types in function declarations. If the withdraw
function is expected to return a bool
to indicate success or failure, its omission could lead to ambiguity or unexpected behavior when interacting with or calling this function from other contracts or off-chain systems. Missing return values can mislead developers and potentially lead to contract integrations built on incorrect assumptions. To resolve this, the function declaration for withdraw
should be modified to explicitly include the bool
return type, ensuring clarity and correctness in contract interactions.
File: DAO.sol 360: function withdrawPOL( IERC20 tokenA, IERC20 tokenB, uint256 percentToLiquidate ) external {
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L360
File: Pools.sol 219: function withdraw( IERC20 token, uint256 amount ) external nonReentrant {
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L219
In Solidity, using >=
or <=
to compare against block.timestamp
(alias now
) may introduce off-by-one errors due to the fact that block.timestamp
is only updated once per block and its value remains constant throughout the block's execution. If an operation happens at the exact second when block.timestamp
changes, it could result in unexpected behavior. To avoid this, it's safer to use strict inequality operators (>
or <
). For instance, if a condition should only be met after a certain time, use block.timestamp > time
rather than block.timestamp >= time
. This way, potential off-by-one errors due to the exact timing of block mining are mitigated, leading to safer, more predictable contract behavior.
File: ManagedWallet.sol 77: require( block.timestamp >= activeTimelock, "Timelock not yet completed" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L77
File: Proposals.sol 83: require( block.timestamp >= firstPossibleProposalTimestamp, "Cannot propose ballots within the first 45 days of deployment" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L83
File: BootstrapBallot.sol 72: require( block.timestamp >= completionTimestamp, "Ballot is not yet complete" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/BootstrapBallot.sol#L72
File: Pools.sol 83: require(block.timestamp <= deadline, "TX EXPIRED");
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L83
File: Staking.sol 105: require( block.timestamp >= u.completionTime, "Unstake has not completed yet" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L105
File: StakingRewards.sol 67: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L67
File: StakingRewards.sol 107: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L107
File: StakingRewards.sol 300: if ( block.timestamp >= cooldownExpiration )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L300
</details>The type of the variable is the same as the type to which the variable is being cast
File: Proposals.sol 217: address(contractAddress)
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L217
If the protocol consider to parse events from the blockchain, it wouldn't be possible to extract a real (unhashed) value from indexed strings. The filter option could be somehow used only for non string-indexed arguments.
At Solidity keccak256 of indexed string is stored when emitting an event. (https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#encoding-of-indexed-event-parameters)
File: DAO.sol 26: event SetContract(string indexed ballotName, address indexed contractAddress);
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L26
Remove indexed
keyword
#0 - c4-judge
2024-02-03T14:00:44Z
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
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | address(this) can be stored in state variable that will ultimately cost less, than every time calculating this, specifically when it used multiple times in a contract | 5 | 250 |
GAS‑2 | Avoid repeating computations | 4 | - |
GAS‑3 | Consider using solady's 'FixedPointMathLib' | 63 | - |
GAS‑4 | Counting down in for statements is more gas efficient | 12 | 3084 |
GAS‑5 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function | 13 | 364 |
GAS‑6 | Using delete statement can save gas | 6 | 48 |
GAS‑7 | Increments can be unchecked to save gas | 27 | 810 |
GAS‑8 | The result of a function call should be cached rather than re-calling the function | 26 | 1300 |
GAS‑9 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 2 | 12 |
GAS‑10 | Use assembly to validate msg.sender | 2 | 24 |
GAS‑11 | Using XOR (^) and AND (&) bitwise equivalents for gas optimizations | 117 | 1521 |
GAS‑12 | Using assembly to check for zero can save gas | 39 | 234 |
GAS‑13 | Using constant s instead of enum can save gas | 1 | - |
GAS‑14 | The more likely conditional require can be checked first | 3 | - |
GAS‑15 | Move proposals.ballotForID(ballotID) call after the conditional checks to save gas | 1 | - |
GAS‑16 | Move uint256 minUnstakePercent = stakingConfig.minUnstakePercent(); call after the conditional checks to save gas | 1 | - |
Total: 379 contexts over 16 issues
address(this)
can be stored in state variable
that will ultimately cost less, than every time calculating this, specifically when it used multiple times in a contract
File: DAO.sol 170: if ( exchangeConfig.salt().balanceOf(address(this)) >= ballot.number1 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L170
File: DAO.sol 246: uint256 saltBalance = exchangeConfig.salt().balanceOf( address(this) );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L246
File: DAO.sol 300: uint256 depositedWETH = pools.depositedUserBalance(address(this), weth ); 307: withdrawnAmount = weth.balanceOf( address(this) );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L300
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L307
File: DAO.sol 365: uint256 liquidityHeld = collateralAndLiquidity.userShareForPool( address(this), poolID );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L365
</details>The following instances show only the first instance of the repeated computations.
File: DAO.sol //@audit bootstrappingRewards * 2 can be cached 247: require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" ); 265: exchangeConfig.salt().approve( address(liquidityRewardsEmitter), bootstrappingRewards * 2 );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L247-L265
File: PoolMath.sol //@audit zapAmountA * reserveB can be cached //@audit reserveA * zapAmountB can be cached 215: if ( zapAmountA * reserveB > reserveA * zapAmountB ) 219: if ( zapAmountA * reserveB < reserveA * zapAmountB )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L215-L219
</details>Using Solady's 'FixedPointMathLib' for multiplication or division operations in Solidity can lead to significant gas savings. This library is designed to optimize fixed-point arithmetic operations, which are common in financial calculations involving tokens or currencies. By implementing more efficient algorithms and assembly optimizations, 'FixedPointMathLib' minimizes the computational resources required for these operations.
For contracts that frequently perform such calculations, integrating this library can reduce transaction costs, thereby enhancing overall performance and cost-effectiveness. However, developers must ensure compatibility with their existing codebase and thoroughly test for accuracy and expected behavior to avoid any unintended consequences.
File: Upkeep.sol 119: uint256 rewardAmount = withdrawnAmount * daoConfig.upkeepRewardPercent() / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L119
File: Upkeep.sol 152: uint256 amountOfWETH = wethBalance * stableConfig.percentArbitrageProfitsForStablePOL() / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L152
File: Upkeep.sol 165: uint256 amountOfWETH = wethBalance * daoConfig.arbitrageProfitsPercentPOL() / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L165
File: Upkeep.sol 289: return daoWETH * daoConfig.upkeepRewardPercent() / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L289
File: ArbitrageSearch.sol 68: uint256 amountOut = (reservesA1 * midpoint) / (reservesA0 + midpoint); 69: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut); 70: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L68-L70
File: ArbitrageSearch.sol 129: uint256 amountOut = (reservesA1 * bestArbAmountIn) / (reservesA0 + bestArbAmountIn); 130: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut); 131: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L129-L131
File: DAO.sol 348: uint256 saltToBurn = ( remainingSALT * daoConfig.percentPolRewardsBurned() ) / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L348
File: DAO.sol 369: uint256 liquidityToWithdraw = (liquidityHeld * percentToLiquidate) / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L369
File: Proposals.sol 90: uint256 requiredXSalt = ( totalStaked * daoConfig.requiredProposalPercentStakeTimes1000() ) / ( 100 * 1000 );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L90
File: Proposals.sol 202: uint256 maxSendable = balance * 5 / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L202
File: Proposals.sol 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;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L324
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L326
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L329
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L335
File: PoolMath.sol 192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 ); 193: uint256 discriminant = B * B + 4 * A * C; 203: swapAmount = ( sqrtDiscriminant - B ) / ( 2 * A );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L192
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L193
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L203
File: PoolMath.sol 215: if ( zapAmountA * reserveB > reserveA * zapAmountB ) 219: if ( zapAmountA * reserveB < reserveA * zapAmountB )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L215
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolMath.sol#L219
File: Pools.sol 109: uint256 proportionalB = ( maxAmount0 * reserve1 ) / reserve0; 115: addedAmount0 = ( maxAmount1 * reserve0 ) / reserve1; 132: addedLiquidity = (totalLiquidity * addedAmount0) / reserve0; 134: addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L109
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L115
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L132
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L134
File: Pools.sol 179: reclaimedA = ( reserves.reserve0 * liquidityToRemove ) / totalLiquidity; 180: reclaimedB = ( reserves.reserve1 * liquidityToRemove ) / totalLiquidity;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L179-L180
File: Pools.sol 260: amountOut = reserve0 * amountIn / reserve1; 266: amountOut = reserve1 * amountIn / reserve0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L260
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L266
File: Pools.sol 313: swapAmountInValueInETH = ( swapAmountIn * reservesWETH ) / reservesTokenIn;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L313
File: PoolUtils.sol 61: uint256 maxAmountIn = reservesIn * maximumInternalSwapPercentTimes1000 / (100 * 1000);
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L61
File: CoreSaltyFeed.sol 40: return ( reservesUSDS * 10**8 ) / reservesWBTC;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreSaltyFeed.sol#L40
File: CoreSaltyFeed.sol 52: return ( reservesUSDS * 10**18 ) / reservesWETH;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreSaltyFeed.sol#L52
File: CoreUniswapFeed.sol 70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) ); 72: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / p;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L70
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L72
File: CoreUniswapFeed.sol 112: return ( uniswapWETH_USDC * 10**18) / uniswapWBTC_WETH;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L112
File: PriceAggregator.sol 142: if ( (_absoluteDifference(priceA, priceB) * 100000) / averagePrice > maximumPriceFeedPercentDifferenceTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L142
File: Emissions.sol 55: uint256 saltToSend = ( saltBalance * timeSinceLastUpkeep * rewardsConfig.emissionsWeeklyPercentTimes1000() ) / ( 100 * 1000 weeks );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L55
File: RewardsEmitter.sol 121: uint256 amountToAddForPool = ( pendingRewards[poolID] * numeratorMult ) / denominatorMult;
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L121
File: SaltRewards.sol 67: uint256 rewardsForPool = ( liquidityRewardsAmount * profitsForPools[i] ) / totalProfits;
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L67
File: SaltRewards.sol 139: uint256 directRewardsForSaltUSDS = ( saltRewardsToDistribute * rewardsConfig.percentRewardsSaltUSDS() ) / 100; 143: uint256 stakingRewardsAmount = ( remainingRewards * rewardsConfig.stakingRewardsPercent() ) / 100;
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L139
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L143
File: CollateralAndLiquidity.sol 159: uint256 rewardedWBTC = (reclaimedWBTC * rewardPercent) / 100; 160: uint256 rewardedWETH = (reclaimedWETH * rewardPercent) / 100; 167: rewardedWBTC = (rewardedWBTC * maxRewardValue) / rewardValue; 168: rewardedWETH = (rewardedWETH * maxRewardValue) / rewardValue;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L159
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L160
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L167
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L168
File: CollateralAndLiquidity.sol 202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals; 203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals;
File: CollateralAndLiquidity.sol 232: uint256 userWBTC = (reservesWBTC * userCollateralAmount ) / totalCollateralShares; 233: uint256 userWETH = (reservesWETH * userCollateralAmount ) / totalCollateralShares;
File: CollateralAndLiquidity.sol 250: uint256 requiredCollateralValueAfterWithdrawal = ( usdsBorrowedByUsers[wallet] * stableConfig.initialCollateralRatioPercent() ) / 100; 261: return userCollateralAmount * maxWithdrawableValue / userCollateralValue;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L250
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L261
File: CollateralAndLiquidity.sol 280: uint256 maxBorrowableAmount = ( userCollateralValue * 100 ) / stableConfig.initialCollateralRatioPercent();
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L280
File: CollateralAndLiquidity.sol 307: return (( userCollateralValue * 100 ) / usdsBorrowedAmount) < stableConfig.minimumCollateralRatioPercent(); 326: uint256 minCollateralValue = (usdsBorrowedByUsers[wallet] * stableConfig.minimumCollateralRatioPercent()) / 100; 329: uint256 minCollateral = (minCollateralValue * totalCollateralShares) / totalCollateralValue;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L307
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L326
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L329
File: Staking.sol 210: uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) ); 211: return numerator / ( 100 * unstakeRange );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L210-L211
File: StakingRewards.sol 114: uint256 rewardsForAmount = ( totalRewards[poolID] * decreaseShareAmount ) / totalShares[poolID]; 118: uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L114
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L118
File: StakingRewards.sol 243: uint256 rewardsShare = ( totalRewards[poolID] * user.userShare ) / totalShares[poolID];
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L243
</details>for
statements is more gas efficientCounting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.
File: ArbitrageSearch.sol 115: for( uint256 i = 0; i < 8; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/arbitrage/ArbitrageSearch.sol#L115
File: Proposals.sol 423: for( uint256 i = 0; i < _openBallotsForTokenWhitelisting.length(); i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L423
File: PoolStats.sol 65: for( uint256 i = 0; i < poolIDs.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L65
File: PoolStats.sol 81: for( uint256 i = 0; i < poolIDs.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L81
File: PoolStats.sol 106: for( uint256 i = 0; i < poolIDs.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L106
File: RewardsEmitter.sol 60: for( uint256 i = 0; i < addedRewards.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L60
File: RewardsEmitter.sol 116: for( uint256 i = 0; i < poolIDs.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L116
File: SaltRewards.sol 64: for( uint256 i = 0; i < addedRewards.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L64
File: CollateralAndLiquidity.sol 321: for ( uint256 i = startIndex; i <= endIndex; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L321
File: StakingRewards.sol 152: for( uint256 i = 0; i < poolIDs.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L152
File: StakingRewards.sol 185: for( uint256 i = 0; i < addedRewards.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L185
File: StakingRewards.sol 296: for( uint256 i = 0; i < cooldowns.length; i++ ) {
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L296
</details>contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
77011 | 311 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 7040 | 7040 | 7040 | 7040 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
73811 | 295 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 3819 | 3819 | 3819 | 3819 | 1 |
require()
/revert()
Checks Should Be Refactored To A Modifier Or FunctionSaves deployment costs
File: Proposals.sol 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" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L224
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L233
File: SaltRewards.sol 60: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" ); 120: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L60
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L120
File: CollateralAndLiquidity.sol 83: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" ); 98: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" ); 117: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L83
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L98
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L117
File: 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" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L90
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L106
File: StakingRewards.sol 59: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" ); 190: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" ); 67: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" ); 107: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L59
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L190
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L67
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L107
</details>delete
statement can save gasFile: PoolStats.sol 54: _arbitrageProfits[ poolIDs[i] ] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L54
File: CoreChainlinkFeed.sol 30: price = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreChainlinkFeed.sol#L30
File: CollateralAndLiquidity.sol 184: usdsBorrowedByUsers[wallet] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L184
File: Liquidizer.sol 113: usdsThatShouldBeBurned = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L113
File: StakingRewards.sol 151: claimableRewards = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L151
File: StakingRewards.sol 301: cooldowns[i] = 0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L301
</details>unchecked
to save gasUsing unchecked
increments can save gas by bypassing the built-in overflow checks. This can save 30-40 gas per iteration. So it is recommended to use unchecked
increments when overflow is not possible.
File: Proposals.sol 108: ballotID = nextBallotID++;
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L108
File: Proposals.sol 423: for( uint256 i = 0; i < _openBallotsForTokenWhitelisting.length(); i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L423
File: BootstrapBallot.sol 57: startExchangeYes++; 59: startExchangeNo++;
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/BootstrapBallot.sol#L57
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/BootstrapBallot.sol#L59
File: PoolStats.sol 53: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L53
File: PoolStats.sol 65: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L65
File: PoolStats.sol 81: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L81
File: PoolStats.sol 106: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L106
File: PriceAggregator.sol 113: numNonZero++;
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L113
File: RewardsEmitter.sol 60: for( uint256 i = 0; i < addedRewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L60
File: RewardsEmitter.sol 116: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L116
File: RewardsEmitter.sol 146: for( uint256 i = 0; i < rewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L146
File: SaltRewards.sol 64: for( uint256 i = 0; i < addedRewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L64
File: SaltRewards.sol 129: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L129
File: CollateralAndLiquidity.sol 321: for ( uint256 i = startIndex; i <= endIndex; i++ ) 333: liquidatableUsers[count++] = wallet; 338: for ( uint256 i = 0; i < count; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L321
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L333
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L338
File: Staking.sol 67: unstakeID = nextUnstakeID++;
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L67
File: Staking.sol 163: for(uint256 i = start; i <= end; i++) 164: unstakes[index++] = _unstakesByID[ userUnstakes[i]];
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L163-L164
File: StakingRewards.sol 152: for( uint256 i = 0; i < poolIDs.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L152
File: StakingRewards.sol 185: for( uint256 i = 0; i < addedRewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L185
File: StakingRewards.sol 216: for( uint256 i = 0; i < shares.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L216
File: StakingRewards.sol 226: for( uint256 i = 0; i < rewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L226
File: StakingRewards.sol 260: for( uint256 i = 0; i < rewards.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L260
File: StakingRewards.sol 277: for( uint256 i = 0; i < shares.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L277
File: StakingRewards.sol 296: for( uint256 i = 0; i < cooldowns.length; i++ )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L296
</details>External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:
File: Upkeep.sol //@audit payable(exchangeConfig.teamVestingWallet(), instead use the immutable variable 233: uint256 releaseableAmount = VestingWallet(payable(exchangeConfig.teamVestingWallet())).releasable(address(salt)); 236: VestingWallet(payable(exchangeConfig.teamVestingWallet())).release(address(salt));
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L233
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L236
File: DAO.sol //@audit exchangeConfig.salt(), instead use the immutable variable 170: if ( exchangeConfig.salt().balanceOf(address(this)) >= ballot.number1 ) 172: IERC20(exchangeConfig.salt()).safeTransfer( ballot.address1, ballot.number1 ); 189: emit GeoExclusionUpdated(ballot.string1, false, exchangeConfig.accessManager().geoVersion()); 197: exchangeConfig.accessManager().excludedCountriesUpdated(); 199: emit GeoExclusionUpdated(ballot.string1, true, exchangeConfig.accessManager().geoVersion());
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L170
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L172
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L189
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L197
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L199
File: DAO.sol //@audit exchangeConfig calls, instead use the immutable variable 246: uint256 saltBalance = exchangeConfig.salt().balanceOf( address(this) ); 254: poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.wbtc() ); 255: poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.weth() ); 257: bytes32 pool1 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.wbtc() ); 258: bytes32 pool2 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.weth() ); 265: exchangeConfig.salt().approve( address(liquidityRewardsEmitter), bootstrappingRewards * 2 );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L246
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L254
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L255
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L257
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L258
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L265
File: DAO.sol //@audit exchangeConfig.upkeep(), instead use the immutable variable 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" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L318
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L329
File: Proposals.sol //@audit exchangeConfig calls, instead use the immutable variable 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" ); 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" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L124
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L132
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L169
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L182
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L183
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L184
File: Proposals.sol //@audit daoConfig.baseBallotQuorumPercentTimes1000() 324: requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 ); 326: requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 ); 329: requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L324
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L326
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L329
File: Airdrop.sol //@audit numberAuthorized() 60: require(numberAuthorized() > 0, "No addresses authorized to claim airdrop."); 64: saltAmountForEachUser = saltBalance / numberAuthorized();
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/Airdrop.sol#L60
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/Airdrop.sol#L64
</details>uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.23/internals/layout_in_storage.html
Each operation involving a uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8
) as compared to ones involving uint256
, due to the compiler having to clear the higher bits of the memory word before operating on the uint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
File: Pools.sol 35: uint128 reserve0;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L35
File: Pools.sol 36: uint128 reserve1;
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L36
msg.sender
We can use assembly to efficiently validate msg.sender with the least amount of opcodes necessary.
File: DAO.sol 362: require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L362
File: PoolStats.sol 49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L49
Given 4 variables a, b, c and d represented as such:
0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d
To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.
File: AccessManager.sol 43: require( msg.sender == address(dao), "AccessManager.excludedCountriedUpdated only callable by the DAO" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/AccessManager.sol#L43
File: ExchangeConfig.sol 77: if ( wallet == address(dao) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/ExchangeConfig.sol#L77
File: ExchangeConfig.sol 81: if ( wallet == address(airdrop) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/ExchangeConfig.sol#L81
File: ManagedWallet.sol 44: require( msg.sender == mainWallet, "Only the current mainWallet can propose changes" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L44
File: ManagedWallet.sol 49: require( proposedMainWallet == address(0), "Cannot overwrite non-zero proposed mainWallet." );
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L49
File: ManagedWallet.sol 61: require( msg.sender == confirmationWallet, "Invalid sender" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L61
File: ManagedWallet.sol 76: require( msg.sender == proposedMainWallet, "Invalid sender" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L76
File: SigningTools.sol 13: require( signature.length == 65, "Invalid signature length" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/SigningTools.sol#L13
File: SigningTools.sol 28: return (recoveredAddress == EXPECTED_SIGNER);
https://github.com/code-423n4/2024-01-salty/tree/main/src/SigningTools.sol#L28
File: Upkeep.sol 115: if ( withdrawnAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L115
File: Upkeep.sol 148: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L148
File: Upkeep.sol 161: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L161
File: Upkeep.sol 174: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L174
File: DAO.sol 119: if ( winningVote == Vote.INCREASE )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L119
File: DAO.sol 121: else if ( winningVote == Vote.DECREASE )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L121
File: DAO.sol 135: if ( nameHash == keccak256(bytes( "setContract:priceFeed1_confirm" )) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L135
File: DAO.sol 137: else if ( nameHash == keccak256(bytes( "setContract:priceFeed2_confirm" )) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L137
File: DAO.sol 139: else if ( nameHash == keccak256(bytes( "setContract:priceFeed3_confirm" )) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L139
File: DAO.sol 141: else if ( nameHash == keccak256(bytes( "setContract:accessManager_confirm" )) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L141
File: DAO.sol 157: if ( ballot.ballotType == BallotType.UNWHITELIST_TOKEN )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L157
File: DAO.sol 166: else if ( ballot.ballotType == BallotType.SEND_SALT )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L166
File: DAO.sol 178: else if ( ballot.ballotType == BallotType.CALL_CONTRACT )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L178
File: DAO.sol 185: else if ( ballot.ballotType == BallotType.INCLUDE_COUNTRY )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L185
File: DAO.sol 192: else if ( ballot.ballotType == BallotType.EXCLUDE_COUNTRY )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L192
File: DAO.sol 203: else if ( ballot.ballotType == BallotType.SET_CONTRACT )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L203
File: DAO.sol 207: else if ( ballot.ballotType == BallotType.SET_WEBSITE_URL )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L207
File: DAO.sol 210: else if ( ballot.ballotType == BallotType.CONFIRM_SET_CONTRACT )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L210
File: DAO.sol 213: else if ( ballot.ballotType == BallotType.CONFIRM_SET_WEBSITE_URL )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L213
File: DAO.sol 251: require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L251
File: DAO.sol 285: if ( ballot.ballotType == BallotType.PARAMETER )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L285
File: DAO.sol 287: else if ( ballot.ballotType == BallotType.WHITELIST_TOKEN )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L287
File: DAO.sol 297: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.withdrawArbitrageProfits is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L297
File: DAO.sol 301: if ( depositedWETH == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L301
File: DAO.sol 318: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.formPOL is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L318
File: DAO.sol 329: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.processRewardsFromPOL is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L329
File: DAO.sol 337: if ( claimedSALT == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L337
File: DAO.sol 362: require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L362
File: DAO.sol 366: if ( liquidityHeld == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L366
File: Parameters.sol 60: if ( parameterType == ParameterTypes.maximumWhitelistedPools )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L60
File: Parameters.sol 62: else if ( parameterType == ParameterTypes.maximumInternalSwapPercentTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L62
File: Parameters.sol 66: else if ( parameterType == ParameterTypes.minUnstakeWeeks )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L66
File: Parameters.sol 68: else if ( parameterType == ParameterTypes.maxUnstakeWeeks )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L68
File: Parameters.sol 70: else if ( parameterType == ParameterTypes.minUnstakePercent )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L70
File: Parameters.sol 72: else if ( parameterType == ParameterTypes.modificationCooldown )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L72
File: Parameters.sol 76: else if ( parameterType == ParameterTypes.rewardsEmitterDailyPercentTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L76
File: Parameters.sol 78: else if ( parameterType == ParameterTypes.emissionsWeeklyPercentTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L78
File: Parameters.sol 80: else if ( parameterType == ParameterTypes.stakingRewardsPercent )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L80
File: Parameters.sol 82: else if ( parameterType == ParameterTypes.percentRewardsSaltUSDS )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L82
File: Parameters.sol 86: else if ( parameterType == ParameterTypes.rewardPercentForCallingLiquidation )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L86
File: Parameters.sol 88: else if ( parameterType == ParameterTypes.maxRewardValueForCallingLiquidation )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L88
File: Parameters.sol 90: else if ( parameterType == ParameterTypes.minimumCollateralValueForBorrowing )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L90
File: Parameters.sol 92: else if ( parameterType == ParameterTypes.initialCollateralRatioPercent )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L92
File: Parameters.sol 94: else if ( parameterType == ParameterTypes.minimumCollateralRatioPercent )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L94
File: Parameters.sol 96: else if ( parameterType == ParameterTypes.percentArbitrageProfitsForStablePOL )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L96
File: Parameters.sol 100: else if ( parameterType == ParameterTypes.bootstrappingRewards )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L100
File: Parameters.sol 102: else if ( parameterType == ParameterTypes.percentPolRewardsBurned )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L102
File: Parameters.sol 104: else if ( parameterType == ParameterTypes.baseBallotQuorumPercentTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L104
File: Parameters.sol 106: else if ( parameterType == ParameterTypes.ballotDuration )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L106
File: Parameters.sol 108: else if ( parameterType == ParameterTypes.requiredProposalPercentStakeTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L108
File: Parameters.sol 110: else if ( parameterType == ParameterTypes.maxPendingTokensForWhitelisting )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L110
File: Parameters.sol 112: else if ( parameterType == ParameterTypes.arbitrageProfitsPercentPOL )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L112
File: Parameters.sol 114: else if ( parameterType == ParameterTypes.upkeepRewardPercent )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L114
File: Parameters.sol 118: else if ( parameterType == ParameterTypes.maximumPriceFeedPercentDifferenceTimes1000 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L118
File: Parameters.sol 120: else if ( parameterType == ParameterTypes.setPriceFeedCooldown )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L120
File: Proposals.sol 124: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can create a confirmation proposal" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L124
File: Proposals.sol 132: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can mark a ballot as finalized" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L132
File: Proposals.sol 137: if ( ballot.ballotType == BallotType.WHITELIST_TOKEN )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L137
File: Proposals.sol 224: require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L224
File: Proposals.sol 267: if ( ballot.ballotType == BallotType.PARAMETER )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L267
File: Proposals.sol 268: require( (vote == Vote.INCREASE) || (vote == Vote.DECREASE) || (vote == Vote.NO_CHANGE), "Invalid VoteType for Parameter Ballot" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L268
File: Proposals.sol 270: require( (vote == Vote.YES) || (vote == Vote.NO), "Invalid VoteType for Approval Ballot" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L270
File: Proposals.sol 323: if ( ballotType == BallotType.PARAMETER )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L323
File: Proposals.sol 325: else if ( ( ballotType == BallotType.WHITELIST_TOKEN ) || ( ballotType == BallotType.UNWHITELIST_TOKEN ) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L325
File: Proposals.sol 347: if ( ballot.ballotType == BallotType.PARAMETER )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L347
File: Airdrop.sol 48: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Only the BootstrapBallot can call Airdrop.authorizeWallet" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/Airdrop.sol#L48
File: Airdrop.sol 58: require( msg.sender == address(exchangeConfig.initialDistribution()), "Airdrop.allowClaiming can only be called by the InitialDistribution contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/Airdrop.sol#L58
File: InitialDistribution.sol 52: require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/InitialDistribution.sol#L52
File: Pools.sol 72: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Pools.startExchangeApproved can only be called from the BootstrapBallot contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L72
File: Pools.sol 97: if ( ( reserve0 == 0 ) || ( reserve1 == 0 ) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L97
File: Pools.sol 142: require( msg.sender == address(collateralAndLiquidity), "Pools.addLiquidity is only callable from the CollateralAndLiquidity contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L142
File: Pools.sol 172: require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L172
File: Pools.sol 325: if ( swapAmountInValueInETH == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L325
File: PoolsConfig.sol 122: return ( poolID == PoolUtils.STAKED_SALT ) || _whitelist.contains( poolID );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolsConfig.sol#L122
File: PoolStats.sol 49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L49
File: PoolStats.sol 67: if (poolID == poolIDs[i])
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolStats.sol#L67
File: PoolUtils.sol 56: if ( amountIn == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L56
File: CoreUniswapFeed.sol 103: if ((uniswapWBTC_WETH == 0) || (uniswapWETH_USDC == 0 ))
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L103
File: CoreUniswapFeed.sol 122: if ( uniswapWETH_USDC == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L122
File: PriceAggregator.sol 53: if ( priceFeedNum == 1 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L53
File: PriceAggregator.sol 55: else if ( priceFeedNum == 2 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L55
File: PriceAggregator.sol 57: else if ( priceFeedNum == 3 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L57
File: Emissions.sol 42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L42
File: Emissions.sol 44: if ( timeSinceLastUpkeep == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L44
File: Emissions.sol 56: if ( saltToSend == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L56
File: RewardsEmitter.sol 84: require( msg.sender == address(exchangeConfig.upkeep()), "RewardsEmitter.performUpkeep is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L84
File: RewardsEmitter.sol 86: if ( timeSinceLastUpkeep == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L86
File: SaltRewards.sol 60: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L60
File: SaltRewards.sol 70: if ( poolID == saltUSDSPoolID )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L70
File: SaltRewards.sol 108: require( msg.sender == address(exchangeConfig.initialDistribution()), "SaltRewards.sendInitialRewards is only callable from the InitialDistribution contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L108
File: SaltRewards.sol 119: require( msg.sender == address(exchangeConfig.upkeep()), "SaltRewards.performUpkeep is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L119
File: SaltRewards.sol 120: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L120
File: SaltRewards.sol 124: if ( saltRewardsToDistribute == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L124
File: SaltRewards.sol 134: if ( totalProfits == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L134
File: CollateralAndLiquidity.sol 224: if ( userCollateralAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L224
File: CollateralAndLiquidity.sol 246: if ( userCollateralAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L246
File: CollateralAndLiquidity.sol 301: if ( usdsBorrowedAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L301
File: Liquidizer.sol 83: require( msg.sender == address(collateralAndLiquidity), "Liquidizer.incrementBurnableUSDS is only callable from the CollateralAndLiquidity contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L83
File: Liquidizer.sol 104: if ( usdsThatShouldBeBurned == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L104
File: Liquidizer.sol 134: require( msg.sender == address(exchangeConfig.upkeep()), "Liquidizer.performUpkeep is only callable from the Upkeep contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L134
File: USDS.sol 42: require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/USDS.sol#L42
File: Staking.sol 88: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be cancelled" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L88
File: Staking.sol 90: require( msg.sender == u.wallet, "Sender is not the original staker" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L90
File: Staking.sol 104: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be claimed" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L104
File: Staking.sol 106: require( msg.sender == u.wallet, "Sender is not the original staker" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L106
File: Staking.sol 132: require( msg.sender == address(exchangeConfig.airdrop()), "Staking.transferStakedSaltFromAirdropToUser is only callable from the Airdrop contract" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L132
File: Staking.sol 175: if ( unstakeIDs.length == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L175
File: StakingRewards.sol 239: if ( user.userShare == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L239
</details>contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
46099 | 261 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 456 | 456 | 456 | 456 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
42493 | 243 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 430 | 430 | 430 | 430 | 1 |
Using assembly to check for zero can save gas by allowing more direct access to the evm and reducing some of the overhead associated with high-level operations in solidity.
File: Upkeep.sol 115: if ( withdrawnAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L115
File: Upkeep.sol 148: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L148
File: Upkeep.sol 161: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L161
File: Upkeep.sol 174: if ( wethBalance == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/Upkeep.sol#L174
File: DAO.sol 301: if ( depositedWETH == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L301
File: DAO.sol 337: if ( claimedSALT == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L337
File: DAO.sol 366: if ( liquidityHeld == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L366
File: Proposals.sol 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" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L102-L103
File: Proposals.sol 321: require( totalStaked != 0, "SALT staked cannot be zero to determine quorum" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L321
File: Pools.sol 97: if ( ( reserve0 == 0 ) || ( reserve1 == 0 ) )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L97
File: Pools.sol 325: if ( swapAmountInValueInETH == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/Pools.sol#L325
File: PoolUtils.sol 56: if ( amountIn == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/pools/PoolUtils.sol#L56
File: CoreUniswapFeed.sol 103: if ((uniswapWBTC_WETH == 0) || (uniswapWETH_USDC == 0 ))
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L103
File: CoreUniswapFeed.sol 122: if ( uniswapWETH_USDC == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/CoreUniswapFeed.sol#L122
File: PriceAggregator.sol 183: require (price != 0, "Invalid BTC price" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L183
File: PriceAggregator.sol 195: require (price != 0, "Invalid ETH price" );
https://github.com/code-423n4/2024-01-salty/tree/main/src/price_feed/PriceAggregator.sol#L195
File: Emissions.sol 44: if ( timeSinceLastUpkeep == 0 ) 56: if ( saltToSend == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L44
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/Emissions.sol#L56
File: RewardsEmitter.sol 66: if ( amountToAdd != 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L66
File: RewardsEmitter.sol 86: if ( timeSinceLastUpkeep == 0 ) 124: if ( amountToAddForPool != 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L86
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/RewardsEmitter.sol#L124
File: SaltRewards.sol 124: if ( saltRewardsToDistribute == 0 ) 134: if ( totalProfits == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L124
https://github.com/code-423n4/2024-01-salty/tree/main/src/rewards/SaltRewards.sol#L134
File: CollateralAndLiquidity.sol 131: if ( usdsBorrowedByUsers[msg.sender] == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L131
File: CollateralAndLiquidity.sol 224: if ( userCollateralAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L224
File: CollateralAndLiquidity.sol 246: if ( userCollateralAmount == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L246
File: CollateralAndLiquidity.sol 271: if ( userShareForPool( wallet, collateralPoolID ) == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L271
File: CollateralAndLiquidity.sol 301: if ( usdsBorrowedAmount == 0 ) 320: if ( totalCollateralValue != 0 ) 347: if ( numberOfUsersWithBorrowedUSDS() == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L301
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L320
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L347
File: Liquidizer.sol 104: if ( usdsThatShouldBeBurned == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/Liquidizer.sol#L104
File: Staking.sol 175: if ( unstakeIDs.length == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L175
File: StakingRewards.sol 60: require( increaseShareAmount != 0, "Cannot increase zero share" ); 78: if ( existingTotalShares != 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L60
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L78
File: StakingRewards.sol 99: require( decreaseShareAmount != 0, "Cannot decrease zero share" ); 136: if ( claimableRewards != 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L99
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L136
File: StakingRewards.sol 235: if ( totalShares[poolID] == 0 ) 239: if ( user.userShare == 0 )
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L235
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/StakingRewards.sol#L239
</details>constant
s 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.
File: 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 }
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Parameters.sol#L14
require
can be checked firstConditional checks that are more likely to happen should be checked first. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas) in a function that may ultimately revert in the unhappy case.
1: The caller of changeWallets
will most likely will be called by proposedMainWallet
, which makes it wasteful to check two conditional require
.
require( block.timestamp >= activeTimelock, "Timelock not yet completed" );
would be the more relevant check.
File: ManagedWallet.sol function changeWallets() external { require( msg.sender == proposedMainWallet, "Invalid sender" ); require( block.timestamp >= activeTimelock, "Timelock not yet completed" ); mainWallet = proposedMainWallet; confirmationWallet = proposedConfirmationWallet; emit WalletChange(mainWallet, confirmationWallet); activeTimelock = type(uint256).max; proposedMainWallet = address(0); proposedConfirmationWallet = address(0); } }
https://github.com/code-423n4/2024-01-salty/tree/main/src/ManagedWallet.sol#L77
File: ManagedWallet.sol function changeWallets() external { require( block.timestamp >= activeTimelock, "Timelock not yet completed" ); require( msg.sender == proposedMainWallet, "Invalid sender" ); mainWallet = proposedMainWallet; confirmationWallet = proposedConfirmationWallet; emit WalletChange(mainWallet, confirmationWallet); activeTimelock = type(uint256).max; proposedMainWallet = address(0); proposedConfirmationWallet = address(0); } }
2: The caller of distributionApproved
will most likely will be called by bootstrapBallot
, which makes it wasteful to check two conditional require
.
require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" );
would be the more relevant check.
File: InitialDistribution.sol function distributionApproved() external { require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" ); require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" ); salt.safeTransfer( address(emissions), 52 * MILLION_ETHER ); salt.safeTransfer( address(daoVestingWallet), 25 * MILLION_ETHER ); salt.safeTransfer( address(teamVestingWallet), 10 * MILLION_ETHER ); salt.safeTransfer( address(airdrop), 5 * MILLION_ETHER ); airdrop.allowClaiming(); bytes32[] memory poolIDs = poolsConfig.whitelistedPools(); salt.safeTransfer( address(saltRewards), 8 * MILLION_ETHER ); saltRewards.sendInitialSaltRewards(5 * MILLION_ETHER, poolIDs ); } }
https://github.com/code-423n4/2024-01-salty/tree/main/src/launch/InitialDistribution.sol#L53
File: InitialDistribution.sol function distributionApproved() external { require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" ); require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" ); salt.safeTransfer( address(emissions), 52 * MILLION_ETHER ); salt.safeTransfer( address(daoVestingWallet), 25 * MILLION_ETHER ); salt.safeTransfer( address(teamVestingWallet), 10 * MILLION_ETHER ); salt.safeTransfer( address(airdrop), 5 * MILLION_ETHER ); airdrop.allowClaiming(); bytes32[] memory poolIDs = poolsConfig.whitelistedPools(); salt.safeTransfer( address(saltRewards), 8 * MILLION_ETHER ); saltRewards.sendInitialSaltRewards(5 * MILLION_ETHER, poolIDs ); } }
3: The caller of mintTo
will most likely will be called by collateralAndLiquidity
, which makes it wasteful to check two conditional require
.
require( amount > 0, "Cannot mint zero USDS" );
would be the more relevant check.
File: USDS.sol function mintTo( address wallet, uint256 amount ) external { require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" ); require( amount > 0, "Cannot mint zero USDS" ); _mint( wallet, amount ); emit USDSMinted(wallet, amount); }
https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/USDS.sol#L43
File: USDS.sol function mintTo( address wallet, uint256 amount ) external { require( amount > 0, "Cannot mint zero USDS" ); require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" ); _mint( wallet, amount ); emit USDSMinted(wallet, amount); }
proposals.ballotForID(ballotID)
call after the conditional checks to save gasIn _finalizeTokenWhitelisting()
, the call for Ballot memory ballot = proposals.ballotForID(ballotID);
is only used after two require
checks. Gas can be saved by moving the ballot
variable after the require
checks.
File: DAO.sol function _finalizeTokenWhitelisting( uint256 ballotID ) internal { if ( proposals.ballotIsApproved(ballotID ) ) { Ballot memory ballot = proposals.ballotForID(ballotID); uint256 bootstrappingRewards = daoConfig.bootstrappingRewards(); uint256 saltBalance = exchangeConfig.salt().balanceOf( address(this) ); require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" ); uint256 bestWhitelistingBallotID = proposals.tokenWhitelistingBallotWithTheMostVotes(); require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" ); poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.wbtc() ); poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.weth() ); bytes32 pool1 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.wbtc() ); bytes32 pool2 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.weth() ); AddedReward[] memory addedRewards = new AddedReward[](2); addedRewards[0] = AddedReward( pool1, bootstrappingRewards ); addedRewards[1] = AddedReward( pool2, bootstrappingRewards ); exchangeConfig.salt().approve( address(liquidityRewardsEmitter), bootstrappingRewards * 2 ); liquidityRewardsEmitter.addSALTRewards( addedRewards ); emit WhitelistToken(IERC20(ballot.address1)); } proposals.markBallotAsFinalized(ballotID); }
https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/DAO.sol#L251
File: DAO.sol function _finalizeTokenWhitelisting( uint256 ballotID ) internal { if ( proposals.ballotIsApproved(ballotID ) ) { uint256 bootstrappingRewards = daoConfig.bootstrappingRewards(); uint256 saltBalance = exchangeConfig.salt().balanceOf( address(this) ); require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" ); uint256 bestWhitelistingBallotID = proposals.tokenWhitelistingBallotWithTheMostVotes(); require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" ); Ballot memory ballot = proposals.ballotForID(ballotID); poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.wbtc() ); poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.weth() ); bytes32 pool1 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.wbtc() ); bytes32 pool2 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.weth() ); AddedReward[] memory addedRewards = new AddedReward[](2); addedRewards[0] = AddedReward( pool1, bootstrappingRewards ); addedRewards[1] = AddedReward( pool2, bootstrappingRewards ); exchangeConfig.salt().approve( address(liquidityRewardsEmitter), bootstrappingRewards * 2 ); liquidityRewardsEmitter.addSALTRewards( addedRewards ); emit WhitelistToken(IERC20(ballot.address1)); } proposals.markBallotAsFinalized(ballotID); }
uint256 minUnstakePercent = stakingConfig.minUnstakePercent();
call after the conditional checks to save gasIn calculateUnstake()
, the call for uint256 minUnstakePercent = stakingConfig.minUnstakePercent();
is only used after two require
checks. Gas can be saved by moving the minUnstakePercent
variable after the require
checks.
File: Staking.sol function calculateUnstake( uint256 unstakedXSALT, uint256 numWeeks ) public view returns (uint256) { uint256 minUnstakeWeeks = stakingConfig.minUnstakeWeeks(); uint256 maxUnstakeWeeks = stakingConfig.maxUnstakeWeeks(); uint256 minUnstakePercent = stakingConfig.minUnstakePercent(); require( numWeeks >= minUnstakeWeeks, "Unstaking duration too short" ); require( numWeeks <= maxUnstakeWeeks, "Unstaking duration too long" ); uint256 percentAboveMinimum = 100 - minUnstakePercent; uint256 unstakeRange = maxUnstakeWeeks - minUnstakeWeeks; uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) ); return numerator / ( 100 * unstakeRange ); } }
https://github.com/code-423n4/2024-01-salty/tree/main/src/staking/Staking.sol#L204-L205
File: Staking.sol function calculateUnstake( uint256 unstakedXSALT, uint256 numWeeks ) public view returns (uint256) { uint256 minUnstakeWeeks = stakingConfig.minUnstakeWeeks(); uint256 maxUnstakeWeeks = stakingConfig.maxUnstakeWeeks(); require( numWeeks >= minUnstakeWeeks, "Unstaking duration too short" ); require( numWeeks <= maxUnstakeWeeks, "Unstaking duration too long" ); uint256 minUnstakePercent = stakingConfig.minUnstakePercent(); uint256 percentAboveMinimum = 100 - minUnstakePercent; uint256 unstakeRange = maxUnstakeWeeks - minUnstakeWeeks; uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) ); return numerator / ( 100 * unstakeRange ); } }
#0 - c4-judge
2024-02-03T14:23:18Z
Picodes marked the issue as grade-b