Alchemix contest - 0x4non's results

A protocol for self-repaying loans with no liquidation risk.

General Information

Platform: Code4rena

Start Date: 05/05/2022

Pot Size: $125,000 DAI

Total HM: 17

Participants: 62

Period: 14 days

Judge: leastwood

Total Solo HM: 15

Id: 120

League: ETH

Alchemix

Findings Distribution

Researcher Performance

Rank: 24/62

Findings: 2

Award: $305.22

🌟 Selected for report: 0

🚀 Solo Findings: 0

Floating pragma

Multiples files have a floating pragma, consider move to a fixed version. Source; https://swcregistry.io/docs/SWC-103

File AlchemicTokenV2.sol

Missing function event

Critical function setWhitelist and setMaxFlashLoan doesnt emit event

L119

Critical function setMaxFlashLoan doesnt emit event L164

Consider add threashold to _maxFlashLoanAmount

L164

File CrossChainCanonicalAlchemicTokenV2.sol

Shadow variable

Vars name and symbol are defined in the ERC20Upgradeable, consider renaming it to _name and _symbol

--- a/contracts-hardhat/CrossChainCanonicalAlchemicTokenV2.sol
+++ b/contracts-hardhat/CrossChainCanonicalAlchemicTokenV2.sol
@@ -6,13 +6,13 @@ import {AlchemicTokenV2Base} from "./AlchemicTokenV2Base.sol";
 
 contract CrossChainCanonicalAlchemicTokenV2 is CrossChainCanonicalBase, AlchemicTokenV2Base {
   function initialize(
-      string memory name, 
-      string memory symbol, 
+      string memory _name, 
+      string memory _symbol, 
       address[] memory _bridgeTokens
   ) public initializer {
     __CrossChainCanonicalBase_init(
-      name,
-      symbol,
+      _name,
+      _symbol,
       msg.sender,
       _bridgeTokens
     );

File gALCX.sol

Missing function event

Critical function transferOwnership doesnt emit event L39

Unused success variable

Consider use the variable to check success approve

--- a/contracts-hardhat/gALCX.sol
+++ b/contracts-hardhat/gALCX.sol
@@ -61,6 +61,7 @@ contract gALCX is ERC20 {
     /// @notice Approve the staking pool to move funds in this address, can be called by anyone
     function reApprove() public {
         bool success = alcx.approve(address(pools), type(uint).max);
+        require(success, "Failed to approve");
     }
 
     // PUBLIC FUNCTIONS

File CrossChainCanonicalGALCX.sol

Vars name and symbol are defined in the ERC20Upgradeable, consider renaming it to _name and _symbol

--- a/contracts-hardhat/CrossChainCanonicalGALCX.sol
+++ b/contracts-hardhat/CrossChainCanonicalGALCX.sol
@@ -5,13 +5,13 @@ import {CrossChainCanonicalBase} from "./CrossChainCanonicalBase.sol";
 
 contract CrossChainCanonicalGALCX is CrossChainCanonicalBase {
   function initialize(
-      string memory name, 
-      string memory symbol, 
+      string memory _name, 
+      string memory _symbol, 
       address[] memory _bridgeTokens
   ) public initializer {
     __CrossChainCanonicalBase_init(
-      name,
-      symbol,
+      _name,
+      _symbol,
       msg.sender,
       _bridgeTokens
     );

File TransmuterBuffer.sol

Check inputs

L180-L181

Function setWeights shoud assert that tokens and weights arrays has the same length.

Recommendation, add;

    function setWeights(
        address weightToken,
        address[] memory tokens,
        uint256[] memory weights
    ) external override onlyAdmin {
        assert(
            tokens.length == weights.length,
            "Length of tokens and weights must be equal"
        );

Missing event emission

Critical function setWeights should emit event L178

File TransmuterV2.sol

Missing event emission

Critical function setCollateralSource should emit event

L196

#0 - 0xleastwood

2022-06-09T20:57:00Z

All issues are non-critical.

File AlchemicTokenV2.sol

Ownable.sol is imported but not used, consider remove the import<br /> L6

File gALCX.sol

Add constant to var alcx

--- a/contracts-hardhat/gALCX.sol
+++ b/contracts-hardhat/gALCX.sol
@@ -9,7 +9,7 @@ import {IALCXSource} from "./interfaces/IALCXSource.sol";
 /// @title A wrapper for single-sided ALCX staking
 contract gALCX is ERC20 {
 
-    IERC20 public alcx = IERC20(0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF);
+    IERC20 public constant alcx = IERC20(0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF);
     IALCXSource public pools = IALCXSource(0xAB8e74017a8Cc7c15FFcCd726603790d26d7DeCa);
     uint public poolId = 1;
     uint public constant exchangeRatePrecision = 1e18;

Use ++i; to increment variables

You could also use the pattern, this pattern is more gas efficient;

for(uint i = 0; i < N; ) {
  // CODE
  unchecked { i++; }
}

Recommendation

--- a/contracts-full/AlchemistV2.sol
+++ b/contracts-full/AlchemistV2.sol
@@ -987,7 +987,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     /// @param owner The address which owns the account.
     function _preemptivelyHarvestDeposited(address owner) internal {
         Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
-        for (uint256 i = 0; i < depositedTokens.values.length; i++) {
+        for (uint256 i = 0; i < depositedTokens.values.length; ++i) {
             _preemptivelyHarvest(depositedTokens.values[i]);
         }
     }
@@ -1279,7 +1279,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     /// @param owner The address of the account owner.
     function _distributeUnlockedCreditDeposited(address owner) internal {
         Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
-        for (uint256 i = 0; i < depositedTokens.values.length; i++) {
+        for (uint256 i = 0; i < depositedTokens.values.length; ++i) {
             _distributeUnlockedCredit(depositedTokens.values[i]);
         }
     }
@@ -1352,7 +1352,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     /// @param owner The address of the account owner.
     function _poke(address owner) internal {
         Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
-        for (uint256 i = 0; i < depositedTokens.values.length; i++) {
+        for (uint256 i = 0; i < depositedTokens.values.length; ++i) {
             _poke(owner, depositedTokens.values[i]);
         }
     }
@@ -1458,7 +1458,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
         uint256 totalValue = 0;
 
         Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
-        for (uint256 i = 0; i < depositedTokens.values.length; i++) {
+        for (uint256 i = 0; i < depositedTokens.values.length; ++i) {
             address yieldToken             = depositedTokens.values[i];
             address underlyingToken        = _yieldTokens[yieldToken].underlyingToken;
             uint256 shares                 = _accounts[owner].balances[yieldToken];
@@ -1521,7 +1521,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
         int256 debt = _accounts[owner].debt;
 
         Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
-        for (uint256 i = 0; i < depositedTokens.values.length; i++) {
+        for (uint256 i = 0; i < depositedTokens.values.length; ++i) {
             address yieldToken = depositedTokens.values[i];
 
             uint256 currentAccruedWeight = _yieldTokens[yieldToken].accruedWeight;

Use memory instead of storage when only reading variables

source: https://medium.com/coinmonks/ethereum-solidity-memory-vs-storage-which-to-use-in-local-functions-72b593c3703a

--- a/contracts-full/AlchemistV2.sol
+++ b/contracts-full/AlchemistV2.sol
@@ -1241,7 +1241,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The loss in basis points.
     function _loss(address yieldToken) internal view returns (uint256) {
-        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
 
         uint256 amountUnderlyingTokens = _convertYieldTokensToUnderlying(yieldToken, yieldTokenParams.activeBalance);
         uint256 expectedUnderlyingValue = yieldTokenParams.expectedValue;
@@ -1278,7 +1278,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @param owner The address of the account owner.
     function _distributeUnlockedCreditDeposited(address owner) internal {
-        Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
+        Sets.AddressSet memory depositedTokens = _accounts[owner].depositedTokens;
         for (uint256 i = 0; i < depositedTokens.values.length; i++) {
             _distributeUnlockedCredit(depositedTokens.values[i]);
         }
@@ -1311,7 +1311,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
         uint256 amount,
         uint256 minimumAmountOut
     ) internal returns (uint256) {
-        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
 
         ITokenAdapter adapter = ITokenAdapter(yieldTokenParams.adapter);
         address underlyingToken = yieldTokenParams.underlyingToken;
@@ -1351,7 +1351,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @param owner The address of the account owner.
     function _poke(address owner) internal {
-        Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
+        Sets.AddressSet memory depositedTokens = _accounts[owner].depositedTokens;
         for (uint256 i = 0; i < depositedTokens.values.length; i++) {
             _poke(owner, depositedTokens.values[i]);
         }
@@ -1457,7 +1457,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     function _totalValue(address owner) internal view returns (uint256) {
         uint256 totalValue = 0;
 
-        Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
+        Sets.AddressSet memory depositedTokens = _accounts[owner].depositedTokens;
         for (uint256 i = 0; i < depositedTokens.values.length; i++) {
             address yieldToken             = depositedTokens.values[i];
             address underlyingToken        = _yieldTokens[yieldToken].underlyingToken;
@@ -1553,7 +1553,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The virtual active balance.
     function _calculateUnrealizedActiveBalance(address yieldToken) internal view returns (uint256) {
-        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
 
         uint256 activeBalance = yieldTokenParams.activeBalance;
         if (activeBalance == 0) {
@@ -1580,7 +1580,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The amount of unlocked credit available.
     function _calculateUnlockedCredit(address yieldToken) internal view returns (uint256) {
-        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
 
         uint256 pendingCredit = yieldTokenParams.pendingCredit;
         if (pendingCredit == 0) {
@@ -1643,7 +1643,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The amount of underlying tokens.
     function _convertYieldTokensToUnderlying(address yieldToken, uint256 amount) internal view returns (uint256) {
-        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
         ITokenAdapter adapter = ITokenAdapter(yieldTokenParams.adapter);
         return amount * adapter.price() / 10**yieldTokenParams.decimals;
     }
@@ -1655,7 +1655,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The amount of yield tokens.
     function _convertUnderlyingTokensToYield(address yieldToken, uint256 amount) internal view returns (uint256) {
-        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
         ITokenAdapter adapter = ITokenAdapter(yieldTokenParams.adapter);
         return amount * 10**yieldTokenParams.decimals / adapter.price();
     }

Use cache internal variable

--- a/contracts-full/AlchemistV2.sol
+++ b/contracts-full/AlchemistV2.sol
@@ -1224,14 +1224,14 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
         uint256 amount,
         function(uint256, uint256) internal pure returns (uint256) operation
     ) internal {
-        YieldTokenParams memory yieldTokenParams = _yieldTokens[yieldToken];
+        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
 
         uint256 amountUnderlyingTokens = _convertYieldTokensToUnderlying(yieldToken, amount);
         uint256 updatedActiveBalance   = operation(yieldTokenParams.activeBalance, amount);
         uint256 updatedExpectedValue   = operation(yieldTokenParams.expectedValue, amountUnderlyingTokens);
 
-        _yieldTokens[yieldToken].activeBalance = updatedActiveBalance;
-        _yieldTokens[yieldToken].expectedValue = updatedExpectedValue;
+        yieldTokenParams.activeBalance = updatedActiveBalance;
+        yieldTokenParams.expectedValue = updatedExpectedValue;
     }
 
     /// @dev Gets the amount of loss that `yieldToken` has incurred measured in basis points. When the expected
@@ -1456,12 +1456,13 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     /// @return The total value.
     function _totalValue(address owner) internal view returns (uint256) {
         uint256 totalValue = 0;
+        Account storage account = _accounts[owner];
 
-        Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
+        Sets.AddressSet storage depositedTokens = account.depositedTokens;
         for (uint256 i = 0; i < depositedTokens.values.length; i++) {
             address yieldToken             = depositedTokens.values[i];
             address underlyingToken        = _yieldTokens[yieldToken].underlyingToken;
-            uint256 shares                 = _accounts[owner].balances[yieldToken];
+            uint256 shares                 = account.balances[yieldToken];
             uint256 amountUnderlyingTokens = _convertSharesToUnderlyingTokens(yieldToken, shares);
 
             totalValue += _normalizeUnderlyingTokensToDebt(underlyingToken, amountUnderlyingTokens);
@@ -1486,11 +1487,13 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ) internal returns (uint256) {
         uint256 shares = _convertYieldTokensToShares(yieldToken, amount);
 
-        if (_accounts[recipient].balances[yieldToken] == 0) {
-          _accounts[r@@ -379,12 +379,12 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         }
 
         // clear current strats
-        for (uint256 j = 0; j < registeredUnderlyings.length; j++) {
+        for (uint256 j = 0; j < registeredUnderlyings.length; ++j) {
             delete _yieldTokens[registeredUnderlyings[j]];
         }
 
         uint256 numYTokens = supportedYieldTokens.length;
-        for (uint256 i = 0; i < numYTokens; i++) {
+        for (uint256 i = 0; i < numYTokens; ++i) {
             address yieldToken = supportedYieldTokens[i];
 
             IAlchemistV2.YieldTokenParams memory params = IAlchemistV2(alchemist)
@@ -476,7 +476,7 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         IAlchemistV2(alchemist).poke(address(this));
 
         Weighting storage weighting = weightings[weightToken];
-        for (uint256 j = 0; j < weighting.tokens.length; j++) {
+        for (uint256 j = 0; j < weighting.tokens.length; ++j) {
             address token = weighting.tokens[j];
             uint256 actionAmt = (amount * weighting.weights[token]) / weighting.totalWeight;
             action(token, actionAmt);
depositedTokens.add(yieldToken);
         }
 
-        _accounts[recipient].balances[yieldToken] += shares;
+        account.balances[yieldToken] += shares;
         _yieldTokens[yieldToken].totalShares += shares;
 
         return shares;
@@ -1518,14 +1521,15 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
     ///
     /// @return The amount of debt that the account owned by `owner` will have after an update.
     function _calculateUnrealizedDebt(address owner) internal view returns (int256) {
-        int256 debt = _accounts[owner].debt;
+        Account storage account = _accounts[owner];
+        int256 debt = account.debt;
 
-        Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
+        Sets.AddressSet storage depositedTokens = account.depositedTokens;
         for (uint256 i = 0; i < depositedTokens.values.length; i++) {
             address yieldToken = depositedTokens.values[i];
 
             uint256 currentAccruedWeight = _yieldTokens[yieldToken].accruedWeight;
-            uint256 lastAccruedWeight    = _accounts[owner].lastAccruedWeights[yieldToken];
+            uint256 lastAccruedWeight    = account.lastAccruedWeights[yieldToken];
             uint256 unlockedCredit       = _calculateUnlockedCredit(yieldToken);
 
             currentAccruedWeight += unlockedCredit > 0
@@ -1536,7 +1540,7 @@ contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
                 continue;
             }
 
-            uint256 balance = _accounts[owner].balances[yieldToken];
+            uint256 balance = account.balances[yieldToken];
             uint256 unrealizedCredit = ((currentAccruedWeight - lastAccruedWeight) * balance) / FIXED_POINT_SCALAR;
 
             debt -= SafeCast.toInt256(unrealizedCredit);

File TransmutterBuffer.sol

Remove unnecesary libraries

Since solidity 0.8 got arithmetics checks you dont need to use safemath

--- a/contracts-hardhat/TransmuterBuffer.sol
+++ b/contracts-hardhat/TransmuterBuffer.sol
@@ -3,8 +3,6 @@ pragma solidity ^0.8.11;
 
 import "@openzeppelin/contracts/access/AccessControl.sol";
 import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
-import "@openzeppelin/contracts/utils/math/Math.sol";
-import "@openzeppelin/contracts/utils/math/SafeMath.sol";
 
 import "./base/Errors.sol";
 
@@ -24,7 +22,6 @@ import "./interfaces/IERC20TokenReceiver.sol";
 ///
 /// @notice An interface contract to buffer funds between the Alchemist and the Transmuter
 contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
-    using SafeMath for uint256;
     using FixedPointMath for FixedPointMath.Number;
 
     /// @notice The identifier of the role which maintains other roles.

Use ++i; to increment variables

You could also use the pattern, this pattern is more gas efficient;

for(uint i = 0; i < N; ) {
  // CODE
  unchecked { i++; }
}

Recommendation

--- a/contracts-hardhat/TransmuterBuffer.sol
+++ b/contracts-hardhat/TransmuterBuffer.sol
@@ -169,7 +169,7 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         returns (uint256 totalBuffered)
     {
         totalBuffered = TokenUtils.safeBalanceOf(underlyingToken, address(this));
-        for (uint256 i = 0; i < _yieldTokens[underlyingToken].length; i++) {
+        for (uint256 i = 0; i < _yieldTokens[underlyingToken].length; ++i) {
             totalBuffered += _getTotalBuffered(_yieldTokens[underlyingToken][i]);
         }
     }
@@ -183,7 +183,7 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         Weighting storage weighting = weightings[weightToken];
         delete weighting.tokens;
         weighting.totalWeight = 0;
-        for (uint256 i = 0; i < tokens.length; i++) {
+        for (uint256 i = 0; i < tokens.length; ++i) {
             address yieldToken = tokens[i];
 
             // For any weightToken that is not the debtToken, we want to verify that the yield-tokens being
@@ -232,14 +232,14 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         sources[_alchemist] = true;
 
         if (alchemist != address(0)) {
-            for (uint256 i = 0; i < registeredUnderlyings.length; i++) {
+            for (uint256 i = 0; i < registeredUnderlyings.length; ++i) {
                 TokenUtils.safeApprove(registeredUnderlyings[i], alchemist, 0);
             }
             TokenUtils.safeApprove(debtToken, alchemist, 0);
         }
 
         alchemist = _alchemist;
-        for (uint256 i = 0; i < registeredUnderlyings.length; i++) {
+        for (uint256 i = 0; i < registeredUnderlyings.length; ++i) {
             TokenUtils.safeApprove(registeredUnderlyings[i], alchemist, type(uint256).max);
         }
         TokenUtils.safeApprove(debtToken, alchemist, type(uint256).max);
@@ -269,7 +269,7 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         }
 
         // only add to the array if not already contained in it
-        for (uint256 i = 0; i < registeredUnderlyings.length; i++) {
+        for (uint256 i = 0; i < registeredUnderlyings.length; ++i) {
             if (registeredUnderlyings[i] == underlyingToken) {
                 revert IllegalState();
             }
@@ -379,12 +379,12 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         }
 
         // clear current strats
-        for (uint256 j = 0; j < registeredUnderlyings.length; j++) {
+        for (uint256 j = 0; j < registeredUnderlyings.length; ++j) {
             delete _yieldTokens[registeredUnderlyings[j]];
         }
 
         uint256 numYTokens = supportedYieldTokens.length;
-        for (uint256 i = 0; i < numYTokens; i++) {
+        for (uint256 i = 0; i < numYTokens; ++i) {
             address yieldToken = supportedYieldTokens[i];
 
             IAlchemistV2.YieldTokenParams memory params = IAlchemistV2(alchemist)
@@ -476,7 +476,7 @@ contract TransmuterBuffer is ITransmuterBuffer, AccessControl, Initializable {
         IAlchemistV2(alchemist).poke(address(this));
 
         Weighting storage weighting = weightings[weightToken];
-        for (uint256 j = 0; j < weighting.tokens.length; j++) {
+        for (uint256 j = 0; j < weighting.tokens.length; ++j) {
             address token = weighting.tokens[j];
             uint256 actionAmt = (amount * weighting.weights[token]) / weighting.totalWeight;
             action(token, actionAmt);

File EthAssetManager.sol

Use ++i; to increment variables

You could also use the pattern, this pattern is more gas efficient;

for(uint i = 0; i < N; ) {
  // CODE
  unchecked { i++; }
}

Recommendation

--- a/contracts-full/EthAssetManager.sol
+++ b/contracts-full/EthAssetManager.sol
@@ -211,7 +211,7 @@ contract EthAssetManager is Multicall, Mutex, IERC20TokenReceiver {
         convexRewards    = params.convexRewards;
         convexPoolId     = params.convexPoolId;
 
-        for (uint256 i = 0; i < NUM_META_COINS; i++) {
+        for (uint256 i = 0; i < NUM_META_COINS; ++i) {
             _metaPoolAssetCache[i] = params.metaPool.coins(i);
             if (_metaPoolAssetCache[i] == IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
                 _metaPoolAssetCache[i] = weth;
@@ -564,7 +564,7 @@ contract EthAssetManager is Multicall, Mutex, IERC20TokenReceiver {
         IERC20[NUM_META_COINS] memory tokens = _metaPoolAssetCache;
 
         uint256 total = 0;
-        for (uint256 i = 0; i < NUM_META_COINS; i++) {
+        for (uint256 i = 0; i < NUM_META_COINS; ++i) {
             // Skip over approving WETH since we are directly swapping ETH.
             if (i == uint256(MetaPoolAsset.ETH)) continue;
 

File ThreePoolAssetManager.sol

Use ++i; to increment variables

You could also use the pattern, this pattern is more gas efficient;

for(uint i = 0; i < N; ) {
  // CODE
  unchecked { i++; }
}

Recommendation

--- a/contracts-full/ThreePoolAssetManager.sol
+++ b/contracts-full/ThreePoolAssetManager.sol
@@ -247,11 +247,11 @@ contract ThreePoolAssetManager is Multicall, Mutex, IERC20TokenReceiver {
         convexRewards     = params.convexRewards;
         convexPoolId      = params.convexPoolId;
 
-        for (uint256 i = 0; i < NUM_STABLE_COINS; i++) {
+        for (uint256 i = 0; i < NUM_STABLE_COINS; ++i) {
             _threePoolAssetCache[i] = params.threePool.coins(i);
         }
 
-        for (uint256 i = 0; i < NUM_META_COINS; i++) {
+        for (uint256 i = 0; i < NUM_META_COINS; ++i) {
             _metaPoolAssetCache[i] = params.metaPool.coins(i);
         }
 
@@ -350,7 +350,7 @@ contract ThreePoolAssetManager is Multicall, Mutex, IERC20TokenReceiver {
 
         uint256 previousBalance;
 
-        for (uint256 i = 0; i < 256; i++) {
+        for (uint256 i = 0; i < 256; ++i) {
             uint256 examineBalance;
             if ((examineBalance = (v.maximum + v.minimum) / 2) == previousBalance) break;
 
@@ -770,7 +770,7 @@ contract ThreePoolAssetManager is Multicall, Mutex, IERC20TokenReceiver {
         uint256 threePoolDecimals = SafeERC20.expectDecimals(address(threePoolToken));
         uint256 normalizedTotal   = 0;
 
-        for (uint256 i = 0; i < NUM_STABLE_COINS; i++) {
+        for (uint256 i = 0; i < NUM_STABLE_COINS; ++i) {
             if (amounts[i] == 0) continue;
 
             uint256 tokenDecimals   = SafeERC20.expectDecimals(address(tokens[i]));
@@ -899,7 +899,7 @@ contract ThreePoolAssetManager is Multicall, Mutex, IERC20TokenReceiver {
         IERC20[NUM_META_COINS] memory tokens = _metaPoolAssetCache;
 
         uint256 total = 0;
-        for (uint256 i = 0; i < NUM_META_COINS; i++) {
+        for (uint256 i = 0; i < NUM_META_COINS; ++i) {
             if (amounts[i] == 0) continue;
 
             total += amounts[i];

File StakingPools.sol

Replace variable with internal cache on loops

Recommendation

--- a/contracts-hardhat/StakingPools.sol
+++ b/contracts-hardhat/StakingPools.sol
@@ -185,7 +185,8 @@ contract StakingPools is IStakingPools, ReentrancyGuard {
     _updatePools();
 
     uint256 _totalRewardWeight = _ctx.totalRewardWeight;
-    for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) {
+    uint256 _length = _pools.length();
+    for (uint256 _poolId = 0; _poolId < _length; _poolId++) {
       Pool.Data storage _pool = _pools.get(_poolId);
 
       uint256 _currentRewardWeight = _pool.rewardWeight;
@@ -360,7 +361,8 @@ contract StakingPools is IStakingPools, ReentrancyGuard {
 
   /// @dev Updates all of the pools.
   function _updatePools() internal {
-    for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) {
+    uint256 _length = _pools.length();
+    for (uint256 _poolId = 0; _poolId < _length; _poolId++) {
       Pool.Data storage _pool = _pools.get(_poolId);
       _pool.update(_ctx);
     }

Use ++i; to increment variables

You could also use the pattern, this pattern is more gas efficient;

for(uint i = 0; i < N; ) {
  // CODE
  unchecked { i++; }
}

Recommendation

--- a/contracts-hardhat/StakingPools.sol
+++ b/contracts-hardhat/StakingPools.sol
@@ -185,7 +185,7 @@ contract StakingPools is IStakingPools, ReentrancyGuard {
     _updatePools();
 
     uint256 _totalRewardWeight = _ctx.totalRewardWeight;
-    for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) {
+    for (uint256 _poolId = 0; _poolId < _pools.length(); ++_poolId) {
       Pool.Data storage _pool = _pools.get(_poolId);
 
       uint256 _currentRewardWeight = _pool.rewardWeight;
@@ -360,7 +360,7 @@ contract StakingPools is IStakingPools, ReentrancyGuard {
 
   /// @dev Updates all of the pools.
   function _updatePools() internal {
-    for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) {
+    for (uint256 _poolId = 0; _poolId < _pools.length(); ++_poolId) {
       Pool.Data storage _pool = _pools.get(_poolId);
       _pool.update(_ctx);
     }
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