Salty.IO - Rolezn's results

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

General Information

Platform: Code4rena

Start Date: 16/01/2024

Pot Size: $80,000 USDC

Total HM: 37

Participants: 178

Period: 14 days

Judge: Picodes

Total Solo HM: 4

Id: 320

League: ETH

Salty.IO

Findings Distribution

Researcher Performance

Rank: 124/178

Findings: 2

Award: $32.48

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA Summary<a name="QA Summary">

QA Issues

IssueContexts
QA‑1address wallet in USDS.mintTo() can be address(collateralAndLiquidity)1
QA‑2ecrecover may return empty address1
QA‑3forceApprove should be used instead of approve4
QA‑4The Contract Should approve(0) First8
QA‑5address shouldn't be hard-coded1
QA‑6Change contract's mint model1
QA‑7Functions calling contracts/addresses with transfer hooks are missing reentrancy guards2
QA‑8A function which defines named returns in it's declaration doesn't need to use return42
QA‑9Use delete to Clear Variables6
QA‑10Incorrect withdraw declaration2
QA‑11Off-by-one timestamp error8
QA‑12Redundant Cast1
QA‑13Indexed strings should be removed1

Total: 78 contexts over 13 issues

QA Issues

<a href="#qa-summary">[QA‑1]</a><a name="QA&#x2011;1"> 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);

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑2]</a><a name="QA&#x2011;2"> ecrecover may return empty address

There 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.

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑3]</a><a name="QA&#x2011;3"> forceApprove should be used instead of approve

In order to prevent front running, forceApprove should be used in place of approve where possible

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑4]</a><a name="QA&#x2011;4"> The Contract Should approve(0) First

Some 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.

<ins>Proof Of Concept</ins>
<details>
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>
<ins>Recommended Mitigation Steps</ins>

Approve with a zero amount first before setting the actual amount.

Non Critical Issues

<a href="#qa-summary">[QA‑5]</a><a name="QA&#x2011;5"> address shouldn't be hard-coded

It is often better to declare addresses 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.

<ins>Proof Of Concept</ins>
File: SigningTools.sol

7: address constant public EXPECTED_SIGNER = 0x1234519DCA2ef23207E1CA7fd70b96f281893bAa;

https://github.com/code-423n4/2024-01-salty/tree/main/src/SigningTools.sol#L7

<a href="#qa-summary">[QA‑6]</a><a name="QA&#x2011;6"> Change contract's mint model

Instead 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.

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑7]</a><a name="QA&#x2011;7"> Functions calling contracts/addresses with transfer hooks are missing reentrancy guards

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.

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑8]</a><a name="QA&#x2011;8"> A function which defines named returns in it's declaration doesn't need to use return

Remove the return statement once ensuring it is safe to do so

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#qa-summary">[QA‑9]</a><a name="QA&#x2011;9"> Use delete to Clear Variables

delete 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.

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#qa-summary">[QA‑10]</a><a name="QA&#x2011;10"> Incorrect withdraw declaration

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.

<ins>Proof Of Concept</ins>
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

<a href="#qa-summary">[QA‑11]</a><a name="QA&#x2011;11"> Off-by-one timestamp error

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.

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#qa-summary">[QA‑12]</a><a name="QA&#x2011;12"> Redundant Cast

The type of the variable is the same as the type to which the variable is being cast

<ins>Proof Of Concept</ins>
File: Proposals.sol

217: address(contractAddress)

https://github.com/code-423n4/2024-01-salty/tree/main/src/dao/Proposals.sol#L217

<a href="#qa-summary">[QA‑13]</a><a name="QA&#x2011;13"> Indexed strings

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)

<ins>Proof Of Concept</ins>
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

<ins>Recommended Mitigation Steps</ins>

Remove indexed keyword

#0 - c4-judge

2024-02-03T14:00:44Z

Picodes marked the issue as grade-b

Findings Information

Awards

20.7932 USDC - $20.79

Labels

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

External Links

GAS Summary<a name="GAS Summary">

Gas Optimizations

IssueContextsEstimated Gas Saved
GAS‑1address(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 contract5250
GAS‑2Avoid repeating computations4-
GAS‑3Consider using solady's 'FixedPointMathLib'63-
GAS‑4Counting down in for statements is more gas efficient123084
GAS‑5Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function13364
GAS‑6Using delete statement can save gas648
GAS‑7Increments can be unchecked to save gas27810
GAS‑8The result of a function call should be cached rather than re-calling the function261300
GAS‑9Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead212
GAS‑10Use assembly to validate msg.sender224
GAS‑11Using XOR (^) and AND (&) bitwise equivalents for gas optimizations1171521
GAS‑12Using assembly to check for zero can save gas39234
GAS‑13Using constants instead of enum can save gas1-
GAS‑14The more likely conditional require can be checked first3-
GAS‑15Move proposals.ballotForID(ballotID) call after the conditional checks to save gas1-
GAS‑16Move uint256 minUnstakePercent = stakingConfig.minUnstakePercent(); call after the conditional checks to save gas1-

Total: 379 contexts over 16 issues

Gas Optimizations

<a href="#gas-summary">[GAS‑1]</a><a name="GAS&#x2011;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

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑2]</a><a name="GAS&#x2011;2"> Avoid repeating computations

The following instances show only the first instance of the repeated computations.

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑3]</a><a name="GAS&#x2011;3"> Consider using solady's 'FixedPointMathLib'

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.

<ins>Proof Of Concept</ins>
<details>
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;

https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L202-L203

File: CollateralAndLiquidity.sol

232: uint256 userWBTC = (reservesWBTC * userCollateralAmount ) / totalCollateralShares;

233: uint256 userWETH = (reservesWETH * userCollateralAmount ) / totalCollateralShares;

https://github.com/code-423n4/2024-01-salty/tree/main/src/stable/CollateralAndLiquidity.sol#L232-L233

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>

<a href="#gas-summary">[GAS‑4]</a><a name="GAS&#x2011;4"> Counting down in for statements is more gas efficient

Counting 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.

<ins>Proof Of Concept</ins>
<details>
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>
Test Code
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; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
77011311
Function Nameminavgmedianmax# calls
AddNum70407040704070401
Contract1 contract
Deployment CostDeployment Size
73811295
Function Nameminavgmedianmax# calls
AddNum38193819381938191

<a href="#gas-summary">[GAS‑5]</a><a name="GAS&#x2011;5"> Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function

Saves deployment costs

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑6]</a><a name="GAS&#x2011;6"> Using delete statement can save gas

<ins>Proof Of Concept</ins>
<details>
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: 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>

<a href="#gas-summary">[GAS‑7]</a><a name="GAS&#x2011;7"> Increments can be unchecked to save gas

Using 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.

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑8]</a><a name="GAS&#x2011;8"> The result of a function call should be cached rather than re-calling the function

External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑9]</a><a name="GAS&#x2011;9"> Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When 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

<ins>Proof Of Concept</ins>
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

<a href="#gas-summary">[GAS‑10]</a><a name="GAS&#x2011;10"> Use assembly to validate msg.sender

We can use assembly to efficiently validate msg.sender with the least amount of opcodes necessary.

<ins>Proof Of Concept</ins>
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

<a href="#gas-summary">[GAS‑11]</a><a name="GAS&#x2011;11"> Using XOR (^) and AND (&) bitwise equivalents for gas optimizations

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.

<ins>Proof Of Concept</ins>
<details>
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>
Test Code
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; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
46099261
Function Nameminavgmedianmax# calls
not_optimized4564564564561
Contract1 contract
Deployment CostDeployment Size
42493243
Function Nameminavgmedianmax# calls
optimized4304304304301

<a href="#gas-summary">[GAS‑12]</a><a name="GAS&#x2011;12"> Using assembly to check for zero can save gas

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.

<ins>Proof Of Concept</ins>
<details>
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>

<a href="#gas-summary">[GAS‑13]</a><a name="GAS&#x2011;13"> Using constants instead of enum can save gas

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

<ins>Proof Of Concept</ins>
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

<a href="#gas-summary">[GAS‑14]</a><a name="GAS&#x2011;14"> The more likely conditional require can be checked first

Conditional 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.

<ins>Proof Of Concept</ins>

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

Optimized Code
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

Optimized Code
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

Optimized Code
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);
		}

<a href="#gas-summary">[GAS‑15]</a><a name="GAS&#x2011;15"> Move proposals.ballotForID(ballotID) call after the conditional checks to save gas

In _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.

<ins>Proof Of Concept</ins>
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

Optimized Code
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);
		}

<a href="#gas-summary">[GAS‑16]</a><a name="GAS&#x2011;16"> Move uint256 minUnstakePercent = stakingConfig.minUnstakePercent(); call after the conditional checks to save gas

In 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.

<ins>Proof Of Concept</ins>
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

Optimized Code
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

AuditHub

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

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter