Maia DAO Ecosystem - 0xn006e7's results

Efficient liquidity renting and management across chains with Curvenized Uniswap V3.

General Information

Platform: Code4rena

Start Date: 30/05/2023

Pot Size: $300,500 USDC

Total HM: 79

Participants: 101

Period: about 1 month

Judge: Trust

Total Solo HM: 36

Id: 242

League: ETH

Maia DAO Ecosystem

Findings Distribution

Researcher Performance

Rank: 44/101

Findings: 1

Award: $610.33

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

610.3258 USDC - $610.33

Labels

bug
disagree with severity
G (Gas Optimization)
grade-a
sponsor confirmed
G-20

External Links

Table of contents

S.N.IssuesInstancesGas Savings
G-01Using XOR (^) and OR (|) bitwise equivalents173
G-02Use a positive conditional flow to save a NOT opcode412
G-03Emitting events can be rearranged to save gas92244
G-04Don't calculate constants4-
G-05Gas savings can be achieved by changing the model for assigning value to the storage structure10-
G-06Avoid caching the value of a particular key of a mapping/member of a struct when that cache is being used only once75409
G-07Avoid caching the result of a function call when that result is being used only once4350
G-08Cache calldata/memory pointers for complex types to avoid complex offset calculations32-
G-09Fail as early as possible15-
G-10Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array1465840
G-11Use already instantiated storage reference rather than repeatedly fetching the value in a mapping or array5200

[G-01] : Using XOR (^) and OR (|) bitwise equivalents

Estimated gas savings according to remix : 73

Note : No test has been provided for the function used in this instance from protocol's side(using which gas savings could be calculated).That's why Remix has been used to demonstrate gas savings via the given POC

On Remix, given only uint256 types, the following are logical equivalents, but don’t cost the same amount of gas:

    (a != b || c != d || e != f) costs 571
    ((a ^ b) | (c ^ d) | (e ^ f)) != 0 costs 498 // (saving 73 gas)

Logic POC

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:

      function xOrEquivalence(uint a, uint b) external returns (bool) {
        //return a != b; //370
        //return a ^ b != 0; //370
      }

However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values:

    function xOrOrEquivalence(uint a, uint b, uint c, uint d) external returns (bool) {
        //return (a != b || c != d); // 495
        //return (a ^ b | c ^ d) != 0; // 442
    }

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.

Coded Proof of Concept :

This little POC also proves that the formulas are equivalent:

    function test_XorEq(uint a, uint b, uint c, uint d, uint e, uint f) external {
        assert((a != b || c != d || e != f) == ((a ^ b) | (c ^ d) | (e ^ f)) != 0);
    }

Consider applying the suggested equivalence and add a comment mentioning what this is equivalent to, as this is less human-readable, but still understandable once it’s been taught.

1 instance in 1 file

1) src/ulysses-omnichain/BranchBridgeAgent.sol :: _depositAndCallMultiple()

File: src/ulysses-omnichain/BranchBridgeAgent.sol
813:         if (
814:             _hTokens.length != _tokens.length || _tokens.length != _amounts.length
815:                 || _amounts.length != _deposits.length
816:         ) revert InvalidInput();

recommended code :

813:         if (
814:               ((_hTokens.length ^ _tokens.length) | ( _tokens.length ^ _amounts.length) |
815:                 | (_amounts.length ^ _deposits.length)) != 0     
816:         ) revert InvalidInput();

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L814-L815

[G-02] : Use a positive conditional flow to save a NOT opcode

By switiching to a positive conditional flow in if statement, a NOT opcode(costing 3 gas) can be saved

4 instances in 2 files

Estimated gas savings : minimum (3 * 4 = 12)

1) src/ulysses-omnichain/RootBridgeAgent.sol :: anyExecute()

File: src/ulysses-omnichain/RootBridgeAgent.sol
1146:             if (!executionHistory[fromChainId][uint32(bytes4(data[1:5]))]) {
1147:                 //Toggle Nonce as executed
1148:                 executionHistory[fromChainId][nonce] = true;
1149: 
1150:                 //Retry failed fallback
1151:                 (success, result) = (false, "");
1152:             } else {
1153:                 _forceRevert();
1154:                 //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure
1155:                 return (true, "already executed tx");
1156:             }

recommended code :

1146:             if (executionHistory[fromChainId][uint32(bytes4(data[1:5]))]) {
1147:                 _forceRevert();
1148:                 //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure
1149:                 return (true, "already executed tx");
1150:             } else {   
1151:                 //Toggle Nonce as executed
1152:                 executionHistory[fromChainId][nonce] = true;
1153:                
1154:                //Retry failed fallback
1155:                (success, result) = (false, "");
1156:            }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootBridgeAgent.sol#L1146

2) src/ulysses-omnichain/CoreBranchRouter.sol :: _toggleBranchBridgeAgentFactory()

File: src/ulysses-omnichain/CoreBranchRouter.sol
164:         if (!IPort(localPortAddress).isBridgeAgentFactory(_newBridgeAgentFactoryAddress)) {
165:             IPort(localPortAddress).addBridgeAgentFactory(_newBridgeAgentFactoryAddress);
166:         } else {
167:             IPort(localPortAddress).toggleBridgeAgentFactory(_newBridgeAgentFactoryAddress);
168:         }

recommended code :

164:         if (IPort(localPortAddress).isBridgeAgentFactory(_newBridgeAgentFactoryAddress)) {
165:             IPort(localPortAddress).toggleBridgeAgentFactory(_newBridgeAgentFactoryAddress);
166:         } else {
167:             IPort(localPortAddress).addBridgeAgentFactory(_newBridgeAgentFactoryAddress);
168:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/CoreBranchRouter.sol#L164

3) src/ulysses-omnichain/CoreBranchRouter.sol :: _manageStrategyToken()

File: src/ulysses-omnichain/CoreBranchRouter.sol
190:         if (!IPort(localPortAddress).isStrategyToken(_underlyingToken)) {
191:             IPort(localPortAddress).addStrategyToken(_underlyingToken, _minimumReservesRatio);
192:         } else {
193:             IPort(localPortAddress).toggleStrategyToken(_underlyingToken);
194:         }

recommended code :

190:         if (IPort(localPortAddress).isStrategyToken(_underlyingToken)) {
191:             IPort(localPortAddress).toggleStrategyToken(_underlyingToken);
192:         } else {
193:             IPort(localPortAddress).toggleStrategyToken(_underlyingToken);
194:             IPort(localPortAddress).addStrategyToken(_underlyingToken, _minimumReservesRatio);
195:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/CoreBranchRouter.sol#L190

4) src/ulysses-omnichain/CoreBranchRouter.sol :: _managePortStrategy()

File: src/ulysses-omnichain/CoreBranchRouter.sol
212:         if (!IPort(localPortAddress).isPortStrategy(_portStrategy, _underlyingToken)) {
213:             //Add new Port Strategy if new.
214:             IPort(localPortAddress).addPortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);
215:         } else if (_isUpdateDailyLimit) {
216:             //Or Update daily limit.
217:             IPort(localPortAddress).updatePortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);
218:         } else {
219:             //Or Toggle Port Strategy.
220:             IPort(localPortAddress).togglePortStrategy(_portStrategy, _underlyingToken);
221:         }

recommended code :

213:         if (IPort(localPortAddress).isPortStrategy(_portStrategy, _underlyingToken)) {
214:             //Toggle Port Strategy
215:             IPort(localPortAddress).togglePortStrategy(_portStrategy, _underlyingToken);
216:         } else if (_isUpdateDailyLimit) {
217:             //Or Update daily limit.
218:             IPort(localPortAddress).updatePortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);
219:         } else {
220:             //Or Add new Port Strategy if new.
221:             IPort(localPortAddress).addPortStrategy(_portStrategy, _underlyingToken, _dailyManagementLimit);       
222:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/CoreBranchRouter.sol#L212

[G-03] : Emitting events can be rearranged to save gas

In the following instances,storage variable is being cached in memory and that cache is being used only once as event paramter.Thus,by rearranging the order in which events are emitted,gas costs incurred due to unnecessary memory operations can be saved.With the suggested arrangement,each time the functions are triggered (~16 gas) gas savings will be achieved.

9 instances in 4 files

Estimated gas savings : 9 * 16 = 144 + 2100(see here)
total estimated gas savings : 2244

1) src/governance/GovernorBravoDelegator.sol :: _setImplementation()

File: src/governance/GovernorBravoDelegator.sol
47:         address oldImplementation = implementation;
48:         implementation = implementation_;
49: 
50:         emit NewImplementation(oldImplementation, implementation);

recommended code :

47:         emit NewImplementation(implementation, implementation_);
48:         implementation = implementation_;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegator.sol#L50

2) src/governance/GovernorBravoDelegateMaia.sol :: _setVotingDelay()

File: src/governance/GovernorBravoDelegateMaia.sol
403:         uint256 oldVotingDelay = votingDelay;
404:         votingDelay = newVotingDelay;
405: 
406:         emit VotingDelaySet(oldVotingDelay, votingDelay);

recommended code :

403:         emit VotingDelaySet(votingDelay, newVotingDelay);
404:         votingDelay = newVotingDelay;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L406

3) src/governance/GovernorBravoDelegateMaia.sol :: _setVotingPeriod()

File: src/governance/GovernorBravoDelegateMaia.sol
417:         uint256 oldVotingPeriod = votingPeriod;
418:         votingPeriod = newVotingPeriod;
419: 
420:         emit VotingPeriodSet(oldVotingPeriod, votingPeriod);

recommended code :

417:         emit VotingPeriodSet(votingPeriod, newVotingPeriod);
418:         votingPeriod = newVotingPeriod;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L422

4) src/governance/GovernorBravoDelegateMaia.sol :: _setProposalThreshold()

File: src/governance/GovernorBravoDelegateMaia.sol
432:         uint256 oldProposalThreshold = proposalThreshold;
433:         proposalThreshold = newProposalThreshold;
434: 
435:         emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);

recommended code :

432:         emit ProposalThresholdSet(proposalThreshold, newProposalThreshold);
433:         proposalThreshold = newProposalThreshold;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L439

5) src/governance/GovernorBravoDelegateMaia.sol :: _setWhitelistGuardian()

File: src/governance/GovernorBravoDelegateMaia.sol
457:         address oldGuardian = whitelistGuardian;
458:         whitelistGuardian = account;
459: 
460:         emit WhitelistGuardianSet(oldGuardian, whitelistGuardian);

recommended code :

457:         emit WhitelistGuardianSet(whitelistGuardian, account);
458:         whitelistGuardian = account;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L466

6) src/governance/GovernorBravoDelegateMaia.sol :: _setPendingAdmin()

File: src/governance/GovernorBravoDelegateMaia.sol
483:         // Save current value, if any, for inclusion in log
484:         address oldPendingAdmin = pendingAdmin;
485: 
486:         // Store pendingAdmin with value newPendingAdmin
487:         pendingAdmin = newPendingAdmin;
488: 
489:         // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
490:         emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);

recommended code :

483:         emit NewPendingAdmin(pendingAdmin, newPendingAdmin);
484: 
485:         // Store pendingAdmin with value newPendingAdmin
486:         pendingAdmin = newPendingAdmin;

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L498

7) src/governance/GovernorBravoDelegateMaia.sol :: _acceptAdmin()

Here, 1 SLOAD(costing 2100) additional gas along with 16 gas can be saved by directly emiitting address(0) instead of pendingAdmin(which is a state variable)

File: src/governance/GovernorBravoDelegateMaia.sol

503:         // Save current values for inclusion in log
504:         address oldAdmin = admin;
505:         address oldPendingAdmin = pendingAdmin;
506: 
507:         // Store admin with value pendingAdmin
508:         admin = pendingAdmin;
509: 
510:         // Clear the pending value
511:         pendingAdmin = address(0);
512: 
513:         emit NewAdmin(oldAdmin, admin);
514:         emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); // @audit : emit `address(0)` directly instead of `pendingAdmin` which is being assigned the value `address(0)` saving 1 SLOAD

recommended code :

503:         // Save current values for inclusion in log
504:         address oldAdmin = admin;
505: 
506:          // Store admin with value pendingAdmin
507:         admin = pendingAdmin;
508: 
509:         emit NewAdmin(oldAdmin, admin);
510: 
511:         emit NewPendingAdmin(pendingAdmin, address(0));
512: 
513:         // Clear the pending value
514:         pendingAdmin = address(0);

https://github.com/code-423n4/2023-05-maia/blob/main/src/governance/GovernorBravoDelegateMaia.sol#L522

8) src/erc-20/ERC20MultiVotes.sol :: setMaxDelegates()

File: src/erc-20/ERC20MultiVotes.sol
97:         uint256 oldMax = maxDelegates;
98:         maxDelegates = newMax;
99: 
100:        emit MaxDelegatesUpdate(oldMax, newMax);

recommended code :

97:         emit MaxDelegatesUpdate(maxDelegates, newMax);
98:         maxDelegates = newMax;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20MultiVotes.sol#L100

9) src/erc-20/ERC20Gauges.sol :: setMaxGaugess()

File: src/erc-20/ERC20Gauges.sol
456:         uint256 oldMax = maxGauges;
457:         maxGauges = newMax;
458: 
459:         emit MaxGaugesUpdate(oldMax, newMax);

recommended code :

456:         emit MaxGaugesUpdate(maxGauges, newMax);
457:         maxGauges = newMax;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L459

[G-04] : Don't calculate constants

Assigning constant variables based on the result of a mathematical calculation wastes gas as constant variables need to be revaluated each time they are accessed.

4 instances in 3 files

1) src/talos/TalosStrategyVanilla.sol :: Line 47

File: src/talos/TalosStrategyVanilla.sol
47:     uint24 private constant protocolFee = 2 * 1e5; //20%

recommended code :

47:     uint24 private constant protocolFee = 200000; //20%

https://github.com/code-423n4/2023-05-maia/blob/main/src/talos/TalosStrategyVanilla.sol#L47

2) src/hermes/minters/BaseV2Minter.sol :: Line 24

File: src/hermes/minters/BaseV2Minter.sol
24:     uint256 internal constant week = 86400 * 7;

recommended code :

24:     uint256 internal constant week = 604800;

https://github.com/code-423n4/2023-05-maia/blob/main/src/hermes/minters/BaseV2Minter.sol#L24

3) src/ulysses-omnichain/lib/AnycallFlags.sol :: Lines 10,11,15 and 16

File: src/ulysses-omnichain/lib/AnycallFlags.sol
10:     uint256 public constant FLAG_PAY_FEE_ON_DEST = 0x1 << 1;
11:     uint256 public constant FLAG_ALLOW_FALLBACK = 0x1 << 2;

15:     uint256 public constant FLAG_EXEC_START_VALUE = 0x1 << 16;
16:     uint256 public constant FLAG_EXEC_FALLBACK = 0x1 << 16;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/lib/AnycallFlags.sol#L10-L11 https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/lib/AnycallFlags.sol#L15-L16

[G-05] : Gas savings can be achieved by changing the model for assigning value to the storage structure

By changing the pattern of assigning value to the storage structure, gas savings can be achieved.In addition, this use will provide significant savings in distribution costs.

10 instances in 6 files

No tests have been provided for the functions used in these instances from protocol's side (using which gas savings could be calculated).

Here is a POC experimented in remix to demonstrate the estimated gas savings.

Estimated gas savings according to remix(for POC only) : 400
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract StructGasTest {
    struct SomeStruct {
        uint256 num;
        bool state;
        address token;
        string name;
        bytes32 value;
    }

    SomeStruct public someStruct;

    function setStruct( uint256 num, bool state, address token, string calldata name, bytes32 value) public {
        // someStruct = SomeStruct({num : num, state : state, token : token, name: name, value: value}); // @audit : costs 38994 gas

        someStruct.num = num;       // @audit : costs 38594 gas
        someStruct.state = state;
        someStruct.token = token;
        someStruct.name = name;
        someStruct.value = value;
    }
}

// paramters used 
// num => 123
// state => true
// token => 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
// name = "hello"
// value = 0x0000000000000000000000000000000011111111111111111111111111111111

1) src/rewards/rewards/FlywheelGaugeRewards.sol :: _queueRewards()

File: src/rewards/rewards/FlywheelGaugeRewards.sol
189:             gaugeQueuedRewards[gauge] = QueuedRewards({
190:                 priorCycleRewards: queuedRewards.priorCycleRewards + completedRewards,
191:                 cycleRewards: uint112(nextRewards),
192:                 storedCycle: currentCycle
193:             });

recommended code :

189:             QueuedRewards storage _gaugeQueuedRewards1 = gaugeQueuedRewards[gauge];
190:             _gaugeQueuedRewards1.priorCycleRewards = queuedRewards.priorCycleRewards + completedRewards;
191:             _gaugeQueuedRewards1.cycleRewards = uint112(nextRewards);
192:             _gaugeQueuedRewards1.storedCycle = currentCycle;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/rewards/FlywheelGaugeRewards.sol#L189-L193

2) src/rewards/rewards/FlywheelGaugeRewards.sol :: getAccruedRewards()

File: src/rewards/rewards/FlywheelGaugeRewards.sol
227:         gaugeQueuedRewards[ERC20(msg.sender)] = QueuedRewards({
228:             priorCycleRewards: 0,
229:             cycleRewards: cycleRewardsNext,
230:             storedCycle: queuedRewards.storedCycle
231:         });

recommended code :

227:         QueuedRewards storage _gaugeQueuedRewards2 = gaugeQueuedRewards[ERC20(msg.sender)];
228:         _gaugeQueuedRewards2.priorCycleRewards = 0;
229:         _gaugeQueuedRewards2.cycleRewards = cycleRewardsNext;
230:         _gaugeQueuedRewards2.storedCycle = queuedRewards.storedCycle;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/rewards/FlywheelGaugeRewards.sol#L228-L232

3) src/ulysses-omnichain/RootPort.sol :: addNewChain()

File: src/ulysses-omnichain/RootPort.sol
473:         getGasPoolInfo[chainId] = GasPoolInfo({
474:             zeroForOneOnInflow: zeroForOneOnInflow,
475:             priceImpactPercentage: _priceImpactPercentage,
476:             gasTokenGlobalAddress: newGlobalToken,
477:             poolAddress: newGasPoolAddress
478:         });

recommended code :

473:         GasPoolInfo storage _gasPoolInfo = getGasPoolInfo[chainId];
474:         _gasPoolInfo.zeroForOneOnInflow = zeroForOneOnInflow;
475:         _gasPoolInfo.priceImpactPercentage = _priceImpactPercentage;
476:         _gasPoolInfo.gasTokenGlobalAddress = newGlobalToken;
477:         _gasPoolInfo.poolAddress = newGasPoolAddress;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L473-L478

4) src/uni-v3-staker/UniswapV3Staker.sol :: onERC721Received()

File: src/uni-v3-staker/UniswapV3Staker.sol
229:         deposits[tokenId] = Deposit({owner: from, tickLower: tickLower, tickUpper: tickUpper, stakedTimestamp: 0});

recommended code :

229:         Deposit storage _deposit = deposits[tokenId];
230:         _deposit.owner = from;
231:         _deposit.tickLower = tickLower;
232:         _deposit.tickUpper = tickUpper;
233:         _deposit.stakedTimestamp = stakedTimestamp;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L229

5) src/uni-v3-staker/UniswapV3Staker.sol :: _stakeToken()

File: src/uni-v3-staker/UniswapV3Staker.sol
511:         if (liquidity >= type(uint96).max) {
512:             _stakes[tokenId][incentiveId] = Stake({
513:                 secondsPerLiquidityInsideInitialX128: secondsPerLiquidityInsideX128,
514:                 liquidityNoOverflow: type(uint96).max,
515:                 liquidityIfOverflow: liquidity
516:             });
517:         } else {
518:             Stake storage stake = _stakes[tokenId][incentiveId];
519:             stake.secondsPerLiquidityInsideInitialX128 = secondsPerLiquidityInsideX128;
520:             stake.liquidityNoOverflow = uint96(liquidity);
521:         }

recommended code :

511:         Stake storage stake = _stakes[tokenId][incentiveId];
512: 
513:          if (liquidity >= type(uint96).max) {
514:             stake.secondsPerLiquidityInsideInitialX128 = secondsPerLiquidityInsideX128;
515:             stake.liquidityNoOverflow = type(uint96).max;
516:             stake.liquidityIfOverflow = liquidity;
517:         } else {
518:             stake.secondsPerLiquidityInsideInitialX128 = secondsPerLiquidityInsideX128;
519:             stake.liquidityNoOverflow = uint96(liquidity);
520:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L507-L511

6) src/ulysses-omnichain/RootBridgeAgent.sol :: _createMultipleSettlement()

File: src/ulysses-omnichain/RootBridgeAgent.sol
531:         getSettlement[_getAndIncrementSettlementNonce()] = Settlement({
532:             owner: _owner,
533:             recipient: _recipient,
534:             hTokens: _hTokens,
535:             tokens: _tokens,
536:             amounts: _amounts,
537:             deposits: _deposits,
538:             callData: _callData,
539:             toChain: _toChain,
540:             status: SettlementStatus.Success,
541:             gasToBridgeOut: userFeeInfo.gasToBridgeOut
542:         });

recommended code :

531:         Settlement storage _settlement = getSettlement[_getAndIncrementSettlementNonce()];
532:         _settlement.owner =  _owner;
533:         _settlement.recipient = _recipient;
534:         _settlement.hTokens = _hTokens;
535:         _settlement.tokens = _tokens;
536:         _settlement.amounts =  _amounts;
537:         _settlement.deposits = _deposits;
538:         _settlement.callData = _callData;
539:         _settlement.toChain = _toChain;
540:         _settlement.status = SettlementStatus.Success;
541:         _settlement.gasToBridgeOut =  userFeeInfo.gasToBridgeOut;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootBridgeAgent.sol#L531-L542

7) src/ulysses-omnichain/BranchBridgeAgent.sol :: _createGasDeposit()

File: src/ulysses-omnichain/BranchBridgeAgent.sol
836:         getDeposit[_getAndIncrementDepositNonce()] = Deposit({
837:             owner: _user,
838:             hTokens: new address[](0),
839:             tokens: new address[](0),
840:             amounts: new uint256[](0),
841:             deposits: new uint256[](0),
842:             status: DepositStatus.Success,
843:             depositedGas: _gasToBridgeOut
844:         });

recommended code :

837:         Deposit storage _deposit1 = getDeposit[_getAndIncrementDepositNonce()];
838:         _deposit1.owner =_user;
839:         _deposit1.hTokens = new address[](0);
840:         _deposit1.tokens = new address[](0);
841:         _deposit1.amounts = new uint256[](0);
842:         _deposit1.deposits = new uint256[](0);
843:         _deposit1.status = DepositStatus.Success;
844:         _deposit1.depositedGas = _gasToBridgeOut;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L836-L844

8) src/ulysses-omnichain/BranchBridgeAgent.sol :: _createDepositSingle()

File: src/ulysses-omnichain/BranchBridgeAgent.sol
882:         getDeposit[_getAndIncrementDepositNonce()] = Deposit({
883:             owner: _user,
884:             hTokens: hTokens,
885:             tokens: tokens,
886:             amounts: amounts,
887:             deposits: deposits,
888:             status: DepositStatus.Success,
889:             depositedGas: _gasToBridgeOut
890:         });

recommended code :

882:         Deposit storage _deposit2 = getDeposit[_getAndIncrementDepositNonce()];
883:         _deposit2.owner = _user;
884:         _deposit2.hTokens = hTokens;
885:         _deposit2.tokens = tokens;
886:         _deposit2.amounts = amounts;
887:         _deposit2.deposits = deposits;
888:         _deposit2.status = DepositStatus.Success;
889:         _deposit2.depositedGas = _gasToBridgeOut;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L882-L890

9) src/ulysses-omnichain/BranchBridgeAgent.sol :: _createDepositMultiple()

File: src/ulysses-omnichain/BranchBridgeAgent.sol
917:         getDeposit[_getAndIncrementDepositNonce()] = Deposit({
918:             owner: _user,
919:             hTokens: _hTokens,
920:             tokens: _tokens,
921:             amounts: _amounts,
922:             deposits: _deposits,
923:             status: DepositStatus.Success,
924:             depositedGas: _gasToBridgeOut
925:         });

recommended code :

917:         Deposit storage _deposit3 = getDeposit[_getAndIncrementDepositNonce()];
918:         _deposit3.owner = _user;
919:         _deposit3.hTokens = _hTokens;
920:         _deposit3.tokens = _tokens;
921:         _deposit3.amounts = _amounts;
922:         _deposit3.deposits = _deposits;
923:         _deposit3.status = DepositStatus.Success;
924:         _deposit3.depositedGas = _gasToBridgeOut;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L918-L926

10) src/erc-20/ERC20Boost.sol :: attach()

File: src/erc-20/ERC20Boost.sol
131:         getUserGaugeBoost[user][msg.sender] =
132:             GaugeState({userGaugeBoost: userGaugeBoost, totalGaugeBoost: totalSupply.toUint128()});

recommended code :

131:         GaugeState storage _gaugeState = getUserGaugeBoost[user][msg.sender];
132:         _gaugeState.userGaugeBoost = userGaugeBoost;
133:         _gaugeState.totalGaugeBoost = totalSupply.toUint128();

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L131-L132

[G-06] : Avoid caching the value of a particular key of a mapping/member of a struct when that cache is being used only once

Caching can save gas only when there are multiple accesses of a particular key of a mapping or a particular memeber of a struct inside a function.There is no point in caching such value when it is being used only once inside a function as it would increase gas costs due to involved memory operations.

7 instances in 4 files

1) src/erc-20/ERC20Gauges.sol :: _addGauge(),this function's gas saving has been calculated by using addGauge function as it is an internal function used by addGauge

avg. gas savings obtained via protocol's test : 2226
functionminavgmedmax#calls
BeforeaddGauge58458103516947359477
AfteraddGauge58455877493517125177

variables newAdd and previouslyDeprecated cached in lines 408 and 409 respectively are being accessed only once in line 411

File: src/erc-20/ERC20Gauges.sol
408:         bool newAdd = _gauges.add(gauge);
409:         bool previouslyDeprecated = _deprecatedGauges.remove(gauge);
410:         // add and fail loud if zero address or already present and not deprecated
411:         if (gauge == address(0) || !(newAdd || previouslyDeprecated)) revert InvalidGaugeError();

recommended code :

File: src/erc-20/ERC20Gauges.sol
408:         // add and fail loud if zero address or already present and not deprecated
409:         if (gauge == address(0) || !(_gauges.add(gauge) || _deprecatedGauges.remove(gauge))) revert InvalidGaugeError();

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L408-L409

2) src/erc-20/ERC20Gauges.sol :: _incrementGaugeWeight(), this function's gas saving has been calculated by using incrementGauges function as it is an internal function used by incrementGauges

avg. gas savings obtained via protocol's test : 16
functionminavgmedmax#calls
BeforeincrementGauges59221435271099412706147
AfterincrementGauges59221435111099192705927

variable added cached in line 210 is being accessed only once in line 211

File: src/erc-20/ERC20Gauges.sol
210:         bool added = _userGauges[user].add(gauge); // idempotent add
211:         if (added && _userGauges[user].length() > maxGauges && !canContractExceedMaxGauges[user]) {
212:             revert MaxGaugeError();
213:         }

recommended code :

210:         if (_userGauges[user].add(gauge) && _userGauges[user].length() > maxGauges && !canContractExceedMaxGauges[user]) {
211:             revert MaxGaugeError();
212:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L210

3) src/erc-20/ERC20Boost.sol :: _addGauge(), _addGauge(),this function's gas saving has been calculated using addGauge function as it is an internal function used by addGauge

gas savings obtained via protocol's test : 3147
functionminavgmedmax#calls
BeforeaddGauge131064241729217292150
AfteraddGauge251461094705787057850

variables newAdd and previouslyDeprecated cached in lines 265 and 266 respectively are being accessed only once in line 268

File: src/erc-20/ERC20Boost.sol
265:         bool newAdd = _gauges.add(gauge);
266:         bool previouslyDeprecated = _deprecatedGauges.remove(gauge);
267:         // add and fail loud if zero address or already present and not deprecated
268:         if (gauge == address(0) || !(newAdd || previouslyDeprecated)) revert InvalidGauge();

recommended code :

265:         // add and fail loud if zero address or already present and not deprecated
266:         if (gauge == address(0) || !(_gauges.add(gauge) || _deprecatedGauges.remove(gauge))) revert InvalidGauge();

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L264-L265

4) src/talos/base/TalosBaseStrategy.sol :: uniswapV3SwapCallback()

avg. gas savings obtained via protocol's test : 11

variable zeroForOne cached in line 337 is being accessed only once in line 339

File: src/talos/base/TalosBaseStrategy.sol
337:         bool zeroForOne = data.zeroForOne;
338: 
339:         if (zeroForOne) address(token0).safeTransfer(msg.sender, uint256(amount0));

recommended code :

337:         if (data.zeroForOne) address(token0).safeTransfer(msg.sender, uint256(amount0));

https://github.com/code-423n4/2023-05-maia/blob/main/src/talos/base/TalosBaseStrategy.sol#L337

5) src/erc-20/ERC20MultiVotes.sol :: _incrementDelegation(), this function's gas saving has been calculated using incrementDelegation function as it is an internal function used by incrementDelegation

avg. gas savinngs obtained via protocol's test : 9
functionminavgmedmax#calls
BeforeincrementDelegation105912738215566116166130
AfterincrementDelegation105912737315565116165130

variable newDelegate cached in line 190 is being accessed only once in line 191

File: src/erc-20/ERC20MultiVotes.sol
190:         bool newDelegate = _delegates[delegator].add(delegatee); // idempotent add
191:         if (newDelegate && delegateCount(delegator) > maxDelegates && !canContractExceedMaxDelegates[delegator]) {
192:             // if is a new delegate, exceeds max and is not approved to exceed, revert
193:             revert DelegationError();
194:         }

recommended code :

190:         if (_delegates[delegator].add(delegatee) && delegateCount(delegator) > maxDelegates && !canContractExceedMaxDelegates[delegator]) {
191:             // if is a new delegate, exceeds max and is not approved to exceed, revert
192:             revert DelegationError();
193:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20MultiVotes.sol#L193

[G-07] : Avoid caching the result of a function call when that result is being used only once

Caching the result of a function call can can save gas only when that result is being accessed mutiple times inside a function .There is no point in caching such result when it is being used only once inside a function as it would increase gas costs due to involved memory operations.

4 instances in 2 files

1) src/erc-20/ERC20Gauges.sol :: _addGauge(), this function's gas saving has been calculated by using addGauge function as it is an internal function used by addGauge

avg. gas savings obtained via protocol's test : 248
functionminavgmedmax#calls
BeforeaddGauge58458103516947359477
AfteraddGauge58457855514367333677

variable currentCycle cached in line 410 is being accessed only once in line 415

File: src/erc-20/ERC20Gauges.sol
410:         uint32 currentCycle = _getGaugeCycleEnd();
411: 
412:         // Check if some previous weight exists and re-add to the total. Gauge and user weights are preserved.
413:         weight = _getGaugeWeight[gauge].currentWeight;
414:         if (weight > 0) {
415:             _writeGaugeWeight(_totalWeight, _add112, weight, currentCycle);
416:         }

recommended code :

410:         // Check if some previous weight exists and re-add to the total. Gauge and user weights are preserved.
411:         weight = _getGaugeWeight[gauge].currentWeight;
412:         if (weight > 0) {
413:             _writeGaugeWeight(_totalWeight, _add112, weight, _getGaugeCycleEnd());
414:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L413

2) src/erc-20/ERC20MultiVotes.sol :: _incrementDelegation(), this function's gas saving has been calculated by using incrementDelegation function as it is an internal function used by incrementDelegation

avg. gas savings obtained via protocol's test : 95
functionminavgmedmax#calls
BeforeincrementDelegation105912738215566116166130
AfterincrementDelegation57212728715564816164830

variable free cached in line 188 is being accessed only once in line 189

File: src/erc-20/ERC20MultiVotes.sol
187:         // Require freeVotes exceed the delegation size
188:         uint256 free = freeVotes(delegator);
189:         if (delegatee == address(0) || free < amount || amount == 0) revert DelegationError();

recommended code :

187:         // Require freeVotes exceed the delegation size
188:         if (delegatee == address(0) || freeVotes(delegator) < amount || amount == 0) revert DelegationError();

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20MultiVotes.sol#L190

3) src/erc-20/ERC20Gauges.sol :: calculateGaugeAllocation()

avg. gas savings obtained via protocol's test : 7
functionminavgmedmax#calls
BeforecalculateGaugeAllocation20572058205720616
AftercalculateGaugeAllocation20492050204920536

variables total and weigth cache in line 178 and 179 respectively are being accessed only once in line 180

File: src/erc-20/ERC20Gauges.sol
178:         uint112 total = _getStoredWeight(_totalWeight, currentCycle);
179:         uint112 weight = _getStoredWeight(_getGaugeWeight[gauge], currentCycle);
180:         return (quantity * weight) / total;

recommended code :

178:         return (quantity * _getStoredWeight(_getGaugeWeight[gauge], currentCycle)) / _getStoredWeight(_totalWeight, currentCycle);

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L178-L180

[G-08] Cache calldata/memory pointers for complex types to avoid complex offset calculations

The function parameters in the following instances are complex types (arrays of structs) and thus will result in more complex offset calculations to retrieve specific data from calldata. We can avoid peforming some of these offset calculations by instantiating calldata/memory pointers.

32 instances in 7 files

No tests have been provided for the functions used in thsese instances from protocol's side (using which gas savings could be calculated).

Here is a POC experimented in remix to demonstrate the estimated gas savings.

Estimated gas savings according to remix(for POC only) : 180

Note : Gas savings will actually be very high since calldata offset calculation is occuring within loops in all of the instances covered in this section

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract CacheComplexTypesGasTest {

    struct Num {
      uint256 a;
      uint256 b;
    }
    
    // @audit : call to `test` costs 3397 gas in CASE 1 while it costs 3217 in CASE 2
    function test(Num[] memory num) public pure returns(uint256) {

        // CASE 1

        // uint256 someNum;
        // uint256 length = num.length;

        // for (uint256 i; i < length;) {  
        //     someNum = num[i].a + num[i].b;

        //     unchecked {
        //         ++i;
        //     }
        // }

        // return someNum;

        // CASE 2

        uint256 someNum;    
        uint256 length = num.length;

        for (uint i; i < length;) {
            Num memory _num = num[i]; 
            someNum = _num.a + _num.b;

            unchecked {
                ++i;
            }
        }

        return someNum;
    } 

    function callTest() public pure {
        Num[] memory num = new Num[](5);
        num[0] = Num({a : 123, b : 456});
        num[1] = Num({a : 321, b : 654});
        num[2] = Num({a : 213, b : 564});
        num[3] = Num({a : 146, b : 365});
        num[4] = Num({a : 254, b : 777});
        test(num);
    }
}

1) src/ulysses-omnichain/VirtualAccount.sol :: call, cache calldata pointer for calls[i]

File: src/ulysses-omnichain/VirtualAccount.sol
48:         for (uint256 i = 0; i < calls.length; i++) {
49:             (bool success, bytes memory data) = calls[i].target.call(calls[i].callData); 
50:             if (!success) revert CallFailed();
51:             returnData[i] = data;
52:         }

recommended code :

48:         for (uint256 i = 0; i < calls.length; i++) {
49:             Call calldata _call = calls[i];
50:             (bool success, bytes memory data) = _call.target.call(_call.callData);
51:             if (!success) revert CallFailed();
52:             returnData[i] = data;
53:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/VirtualAccount.sol#L49

2) src/ulysses-amm/UlyssesRouter.sol :: swap, cache calldata pointer for routes[i]

File: src/ulysses-amm/UlyssesRouter.sol
78:         for (uint256 i = 0; i < length;) {
79:             amount = getUlyssesLP(routes[i].from).swapIn(amount, routes[i].to); 
80: 
81:             unchecked {
82:                 ++i;
83:             }
84:         }

recommended code :

78:         for (uint256 i = 0; i < length;) {
79:             Route calldata _route = routes[i];
80:             amount = getUlyssesLP(_route.from).swapIn(amount, _route.to);
81: 
82:             unchecked {
83:                 ++i;
84:             }
85:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesRouter.sol#L79

3) src/ulysses-amm/UlyssesToken.sol :: setWeights(), cache assets[i] and _weights[i]

File: src/ulysses-amm/UlyssesToken.sol
95:         for (uint256 i = 0; i < assets.length; i++) {
96:             newTotalWeights += _weights[i]; 
97: 
98:             emit AssetRemoved(assets[i]); 
99:             emit AssetAdded(assets[i], _weights[i]); 
100:         }

recommended code :

95:         for (uint256 i = 0; i < assets.length; i++) {
96:             address _asset = assets[i];
97:             uint256 _weight = _weights[i];
98:             newTotalWeights += _weight;
99: 
100:             emit AssetRemoved(_asset);
101:             emit AssetAdded(_asset, _weight);
102:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesToken.sol#L96-L99

4) src/ulysses-amm/factories/UlyssesFactory.sol :: createPools(), cache weights[i][j]

File: src/ulysses-amm/factories/UlyssesFactory.sol
110:             for (uint256 j = 0; j < length;) {
111:                 if (j != i && weights[i][j] > 0) pools[poolIds[i]].addNewBandwidth(poolIds[j], weights[i][j]); 
112: 
113:                 unchecked {
114:                     ++j;
115:                 }
116:             }

recommended code : Similar mitigation steps as shown in above instances can be applied to this one too

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/factories/UlyssesFactory.sol#L111

5) src/ulysses-omnichain/ArbitrumBranchPort.sol :: bridgeOutMultiple(), cache _deposits[i], _amounts[i] and _underlyingAddresses[i]

File: src/ulysses-omnichain/ArbitrumBranchPort.sol
136:         for (uint256 i = 0; i < _localAddresses.length;) {
137:             if (_deposits[i] > 0) { 
138:                 _underlyingAddresses[i].safeTransferFrom( 
139:                     _depositor,
140:                     address(this),
141:                     _denormalizeDecimals(_deposits[i], ERC20(_underlyingAddresses[i]).decimals()) 
142:                 );
143:             }
144:             if (_amounts[i] - _deposits[i] > 0) {
145:                 IRootPort(rootPortAddress).bridgeToRootFromLocalBranch(
146:                     _depositor, _localAddresses[i], _amounts[i] - _deposits[i] 
147:                 );
148:             }
149: 
150:             unchecked {
151:                 ++i;
152:             }
153:         }

recommended code :

136:         for (uint256 i = 0; i < _localAddresses.length;) {
137:             uint256 _deposit = _deposits[i];
138:             uint256 _amount = -_amounts[i];
139:             if (_deposit > 0) {
140:                 address _underlyingAddress = _underlyingAddresses[i];
141:                 _underlyingAddress.safeTransferFrom(
142:                     _depositor,
143:                     address(this),
144:                     _denormalizeDecimals(_deposit, ERC20(_underlyingAddress).decimals())
145:                 );
146:             }
147:             if (_amount - _deposit > 0) {
148:                 IRootPort(rootPortAddress).bridgeToRootFromLocalBranch(
149:                     _depositor, _localAddresses[i], _amount - _deposit
150:                 );
151:             }
152: 
153:             unchecked {
154:                 ++i;
155:             }
156:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/ArbitrumBranchPort.sol#L137-L146

6) src/ulysses-omnichain/BranchPort.sol :: bridgeOutMultiple(), cache _deposits[i],_amounts[i],_underlyingAddresses[i] and _localAddresses[i]

File: src/ulysses-omnichain/BranchPort.sol
267:         for (uint256 i = 0; i < _localAddresses.length;) {
268:             if (_deposits[i] > 0) {
269:                 _underlyingAddresses[i].safeTransferFrom( 
270:                     _depositor,
271:                     address(this),
272:                     _denormalizeDecimals(_deposits[i], ERC20(_underlyingAddresses[i]).decimals()) 
273:                 );
274:             }
275:             if (_amounts[i] - _deposits[i] > 0) { 
276:                 _localAddresses[i].safeTransferFrom(_depositor, address(this), _amounts[i] - _deposits[i]); 
277:                 ERC20hTokenBranch(_localAddresses[i]).burn(_amounts[i] - _deposits[i]);
278:             }
279:             unchecked {
280:                 i++;
281:             }
282:         }

recommended code :

267:         for (uint256 i = 0; i < _localAddresses.length;) {
268:             uint256 _deposit = _deposits[i];
269:             uint256 _amount = -_amounts[i];
270:             if (_deposit > 0) {
271:                 address _underlyingAddress = _underlyingAddresses[i];
272:                 _underlyingAddress.safeTransferFrom(
273:                     _depositor,
274:                     address(this),
275:                     _denormalizeDecimals(_deposit, ERC20(_underlyingAddress).decimals())
276:                 );
277:             }
278:             if (_amount - _deposit > 0) {
279:                 address _localAddress = _localAddresses[i];
280:                 _localAddress.safeTransferFrom(_depositor, address(this), _amount - _deposit);
281:                 ERC20hTokenBranch(_localAddress).burn(_amount - _deposit);
282:             }
283:             unchecked {
284:                 i++;
285:             }
286:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L268-L277

7) src/erc-4626/ERC4626MultiToken.sol :: constructor(), cache _weights[i]

File: src/erc-4626/ERC4626MultiToken.sol
50:         for (uint256 i = 0; i < length;) {
51:             require(ERC20(_assets[i]).decimals() == 18); 
52:             require(_weights[i] > 0); 
53: 
54:             _totalWeights += _weights[i];  
55:             assetId[_assets[i]] = i + 1; 
56: 
57:             emit AssetAdded(_assets[i], _weights[i]); 
58: 
59:             unchecked {
60:                 i++;
61:             }
62:         }

recommended code :

50:         for (uint256 i = 0; i < length;) {
51:             uint256 _asset = _assets[i]; // @audit : _assets[i] is a state variable being read in a loop(this issue is already included in automated finding), this line has been added in this recommendation just so that the code semantics looks similar overall
52:             address _weight = _weights[i];
53:             require(ERC20(_asset).decimals() == 18);
54:             require(_weight > 0);
55: 
56:             _totalWeights += _weight;
57:             assetId[_asset] = i + 1;
58: 
59:             emit AssetAdded(_asset, _weight);
60: 
61:             unchecked {
62:                 i++;
63:             }
64:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/ERC4626MultiToken.sol#L51-L57

[G-09] : Fail as early as possible

Move require statement to the top of the function as it's logic is not influenced by the code above it in any way and in the current code design,users will have to pay more gas if function reverts.Thus by moving require statements to the top,users gas can be saved if they call the function and the function reverts as early as possible

15 instances in 9 files

1) src/ulysses-amm/UlyssesPool.sol :: constructor()

File: src/ulysses-amm/UlyssesPool.sol
88:         require(_owner != address(0));
89:         factory = UlyssesFactory(_factory);
90:         _initializeOwner(_owner);
91:         require(_id != 0);
92:         id = _id;

recommended code :

89:         require(_id != 0);
90:         factory = UlyssesFactory(_factory);
91:         _initializeOwner(_owner);
92:         id = _id;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesPool.sol#L91

2) src/ulysses-amm/UlyssesToken.sol :: constructor()

File: src/ulysses-amm/UlyssesToken.sol
29:         _initializeOwner(_owner);
30:         require(_id != 0);
31:         id = _id;

recommended code :

29:         require(_id != 0);
30:         _initializeOwner(_owner);
31:         id = _id;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesToken.sol#L30

3) src/ulysses-amm/UlyssesToken.sol :: removeAsset()

File: src/ulysses-amm/UlyssesToken.sol
61:         // No need to check if index is 0, it will underflow and revert if it is 0
62:         uint256 assetIndex = assetId[asset] - 1;
63: 
64:         uint256 newAssetsLength = assets.length - 1;
65: 
66:         if (newAssetsLength == 0) revert CannotRemoveLastAsset();

recommended code :

61:         uint256 newAssetsLength = assets.length - 1;
62: 
63:         if (newAssetsLength == 0) revert CannotRemoveLastAsset();
64:         
65:         // No need to check if index is 0, it will underflow and revert if it is 0
66:         uint256 assetIndex = assetId[asset] - 1;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesToken.sol#L66

4) src/uni-v3-staker/UniswapV3Staker.sol ::createIncentiveFromGauge()

File: src/uni-v3-staker/UniswapV3Staker.sol
138:         if (reward <= 0) revert IncentiveRewardMustBePositive();
139: 
140:         uint96 startTime = IncentiveTime.computeEnd(block.timestamp);
141: 
142:         IUniswapV3Pool pool = gaugePool[msg.sender];
143: 
144:         if (address(pool) == address(0)) revert IncentiveCallerMustBeRegisteredGauge();

recommended code :

138:         if (reward <= 0) revert IncentiveRewardMustBePositive();
139: 
140:         IUniswapV3Pool pool = gaugePool[msg.sender];
141: 
142:         if (address(pool) == address(0)) revert IncentiveCallerMustBeRegisteredGauge();
143: 
144:         uint96 startTime = IncentiveTime.computeEnd(block.timestamp);

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L144

5) src/erc-4626/ERC4626MultiToken.sol :: constructor()

File: src/erc-4626/ERC4626MultiToken.sol
42:         assets = _assets;
43:         weights = _weights;
44: 
45:         uint256 length = _weights.length;
46:         uint256 _totalWeights;
47: 
48:         if (length != _assets.length || length == 0) revert InvalidLength();

recommended code :

42:         uint256 length = _weights.length;
43:         if (length != _assets.length || length == 0) revert InvalidLength();
44:         
45:         assets = _assets;
46:         weights = _weights;
47: 
48:         uint256 _totalWeights;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/ERC4626MultiToken.sol#L48

6) src/erc-4626/ERC4626MultiToken.sol :: convertToShares()

File: src/erc-4626/ERC4626MultiToken.sol
197:         uint256 _totalWeights = totalWeights;
198:         uint256 length = assetsAmounts.length;
199: 
200:         if (length != assets.length) revert InvalidLength();

recommended code :

197:         uint256 length = assetsAmounts.length;
198: 
199:         if (length != assets.length) revert InvalidLength();
200:         uint256 _totalWeights = totalWeights;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/ERC4626MultiToken.sol#L198

7) src/erc-4626/ERC4626MultiToken.sol :: previewWithdraw()

File: src/erc-4626/ERC4626MultiToken.sol
247:         uint256 _totalWeights = totalWeights;
248:         uint256 length = assetsAmounts.length;
249: 
250:         if (length != assets.length) revert InvalidLength();

recommended code :

247:         uint256 length = assetsAmounts.length;
248: 
249:         if (length != assets.length) revert InvalidLength();
250:         uint256 _totalWeights = totalWeights;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/ERC4626MultiToken.sol#L248

8) src/erc-4626/UlyssesERC4626.sol :: constructor()

File: src/erc-4626/UlyssesERC4626.sol
25:         asset = _asset;
26: 
27:         if (ERC20(_asset).decimals() != 18) revert InvalidAssetDecimals();

recommended code :

File: src/erc-4626/UlyssesERC4626.sol
25:         if (ERC20(_asset).decimals() != 18) revert InvalidAssetDecimals();
26:         
27:         asset = _asset;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/UlyssesERC4626.sol#L27

9) src/erc-4626/UlyssesERC4626.sol :: deposit()

File: src/erc-4626/UlyssesERC4626.sol
35:         // Need to transfer before minting or ERC777s could reenter.
36:         asset.safeTransferFrom(msg.sender, address(this), assets);
37: 
38:         shares = beforeDeposit(assets);
39: 
40:         require(shares != 0, "ZERO_SHARES");

recommended code :

35:         shares = beforeDeposit(assets);
36: 
37:         require(shares != 0, "ZERO_SHARES");
38: 
39:         // Need to transfer before minting or ERC777s could reenter.
40:         asset.safeTransferFrom(msg.sender, address(this), assets);

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/UlyssesERC4626.sol#L40

10) src/erc-4626/UlyssesERC4626.sol :: redeem()

File: src/erc-4626/UlyssesERC4626.sol
65:         if (msg.sender != owner) {
66:             uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
67: 
68:             if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
69:         }
70: 
71:         _burn(owner, shares);
72: 
73:         assets = afterRedeem(shares);
74: 
75:         require(assets != 0, "ZERO_ASSETS");

recommended code :

65:         assets = afterRedeem(shares);
66: 
67:         require(assets != 0, "ZERO_ASSETS");
68:         
69:         if (msg.sender != owner) {
70:             uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
71: 
72:             if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
73:         }
74: 
75:         _burn(owner, shares);

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/UlyssesERC4626.sol#L75

11) src/erc-4626/ERC4626.sol :: redeem()

File: src/erc-4626/ERC4626.sol
81:         if (msg.sender != owner) {
82:             uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
83: 
84:             if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
85:         }
86: 
87:         // Check for rounding error since we round down in previewRedeem.
88:         require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");

recommended code :

81:         // Check for rounding error since we round down in previewRedeem.
82:         require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
83: 
84:         if (msg.sender != owner) {
85:             uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
86: 
87:             if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
88:         }
89: 

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-4626/ERC4626.sol#L88

12) src/talos/base/TalosBaseStrategy.sol :: redeem()

File: src/talos/base/TalosBaseStrategy.sol
246:         if (msg.sender != _owner) {
247:             uint256 allowed = allowance[_owner][msg.sender]; // Saves gas for limited approvals.
248: 
249:             if (allowed != type(uint256).max) allowance[_owner][msg.sender] = allowed - shares;
250:         }
251: 
252:         if (shares == 0) revert RedeemingZeroShares();
253:         if (receiver == address(0)) revert ReceiverIsZeroAddress();

recommended code :

246:         if (shares == 0) revert RedeemingZeroShares();
247:         if (receiver == address(0)) revert ReceiverIsZeroAddress();
248: 
249:         if (msg.sender != _owner) {
250:             uint256 allowed = allowance[_owner][msg.sender]; // Saves gas for limited approvals.
251: 
252:             if (allowed != type(uint256).max) allowance[_owner][msg.sender] = allowed - shares;
253:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/talos/base/TalosBaseStrategy.sol#L252-253

13) src/ulysses-amm/UlyssesRouter.sol :: swap

File: src/ulysses-amm/UlyssesRouter.sol
74:         address(getUlyssesLP(routes[0].from).asset()).safeTransferFrom(msg.sender, address(this), amount);
75: 
76:         uint256 length = routes.length;
77: 
78:         for (uint256 i = 0; i < length;) {
79:             Route calldata _route = routes[i];
80:             amount = getUlyssesLP(_route.from).swapIn(amount, _route.to);
81: 
82:             unchecked {
83:                 ++i;
84:             }
85:         }
86: 
87:         if (amount < minOutput) revert OutputTooLow();
88: 
89:         unchecked {
90:             --length;
91:         }

recommended code :

74:         if (amount < minOutput) revert OutputTooLow();
75:         
76:         address(getUlyssesLP(routes[0].from).asset()).safeTransferFrom(msg.sender, address(this), amount);
77: 
78:         uint256 length = routes.length;
79: 
80:         for (uint256 i = 0; i < length;) {
81:             Route calldata _route = routes[i];
82:             amount = getUlyssesLP(_route.from).swapIn(amount, _route.to);
83: 
84:             unchecked {
85:                 ++i;
86:             }
87:         }
88: 
89:         unchecked {
90:             --length;
91:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesRouter.sol#L86

14) src/erc-20/ERC20Gauges.sol :: _decrementGaugeWeight()

File: src/erc-20/ERC20Gauges.sol
292:         if (!_gauges.contains(gauge)) revert InvalidGaugeError();
293: 
294:         uint112 oldWeight = getUserGaugeWeight[user][gauge];
295: 
296:         IBaseV2Gauge(gauge).accrueBribes(user);
297: 
298:         getUserGaugeWeight[user][gauge] = oldWeight - weight;
299:         if (oldWeight == weight) {
300:             // If removing all weight, remove gauge from user list.
301:             require(_userGauges[user].remove(gauge));
302:         }

recommended code :

292:         if (!_gauges.contains(gauge)) revert InvalidGaugeError();
293: 
294:         uint112 oldWeight = getUserGaugeWeight[user][gauge];
295: 
296:         if (oldWeight == weight) {
297:             // If removing all weight, remove gauge from user list.
298:             require(_userGauges[user].remove(gauge));
299:         }
300: 
301:         IBaseV2Gauge(gauge).accrueBribes(user);
302: 
303:         getUserGaugeWeight[user][gauge] = oldWeight - weight;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L300-L302

[G-10] : Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array

Caching the value of a particular key of a mapping or the value of a particular index of an array in a local storage variable when the value is accessed(read and written) multiple times saves ~40 gas per access due to not having to perform the same offset calculation every time.Help the Optimizer by saving a storage variable’s reference instead of repeatedly fetching it

To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array.

As an example, instead of repeatedly calling someMap[someKey] and SomeArray[someIndex], save their references like SomeType storage someVariable = someMap[someIndex] and SomeType storage someVariable = SomeArray[someIndex]use them.

146 instances in 18 files

Estimated gas savings : 146 * 40 = 5840

Note : Maximum gas savings can be achieved in instances where the value being accessed multiple times are of complex types(such as array of structs that contains arrays) and especially when they are being accessed inside lengthy for loops

1) src/rewards/depots/MultiRewardsDepot.sol :: addAsset(), _isAsset[asset] and _isRewardsContract[rewardsContract] should be cached in local storage

File: src/rewards/depots/MultiRewardsDepot.sol
48:         if (_isAsset[asset] || _isRewardsContract[rewardsContract]) revert ErrorAddingAsset(); 
49:         _isAsset[asset] = true;
50:         _isRewardsContract[rewardsContract] = true;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/depots/MultiRewardsDepot.sol#L48-L50

2) src/rewards/depots/MultiRewardsDepot.sol :: removeAsset(), _isRewardsContract[rewardsContract] and _assets[rewardsContract] should be cached in local storage

File: src/rewards/depots/MultiRewardsDepot.sol
58:         if (!_isRewardsContract[rewardsContract]) revert ErrorRemovingAsset();
59: 
60:         emit AssetRemoved(rewardsContract, _assets[rewardsContract]);
61: 
62:         delete _isAsset[_assets[rewardsContract]];
63:         delete _isRewardsContract[rewardsContract];
64:         delete _assets[rewardsContract];

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/depots/MultiRewardsDepot.sol#L58-L64

3) src/maia/factories/PartnerManagerFactory.sol :: removePartner(), partnerIds[partnerManager]and partners[partnerIds[partnerManager]] should be cached in local storage

File: src/maia/factories/PartnerManagerFactory.sol
81:         if (partners[partnerIds[partnerManager]] != partnerManager) revert InvalidPartnerManager();
82:         delete partners[partnerIds[partnerManager]];
83:         delete partnerIds[partnerManager];

https://github.com/code-423n4/2023-05-maia/blob/main/src/maia/factories/PartnerManagerFactory.sol#L81-L83

4) src/maia/factories/PartnerManagerFactory.sol :: removeVault(), vaultIds[vault] and vaults[vaultIds[vault]]`` should be cached in local storage

File: src/maia/factories/PartnerManagerFactory.sol
90:         if (vaults[vaultIds[vault]] != vault) revert InvalidVault();
91:         delete vaults[vaultIds[vault]];
92:         delete vaultIds[vault];

https://github.com/code-423n4/2023-05-maia/blob/main/src/maia/factories/PartnerManagerFactory.sol#L90-L92

5) src/ulysses-amm/UlyssesRouter.sol :: getUlyssesLP(), pools[id] should be cached in local storage

File: src/ulysses-amm/UlyssesRouter.sol
32:         ulysses = pools[id];
33:         if (address(ulysses) == address(0)) {
34:             ulysses = ulyssesFactory.pools(id);
35: 
36:             if (address(ulysses) == address(0)) revert UnrecognizedUlyssesLP();
37: 
38:             pools[id] = ulysses;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesRouter.sol#L32-L38

6) src/gauges/factories/BribesFactory.sol :: addGaugetoFlywheel(), flywheelTokens[bribeToken] should be cached in local storage

File: src/gauges/factories/BribesFactory.sol
73:         if (address(flywheelTokens[bribeToken]) == address(0)) createBribeFlywheel(bribeToken);
74: 
75:         flywheelTokens[bribeToken].addStrategyForRewards(ERC20(gauge));

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BribesFactory.sol#L73-L75

7) src/gauges/factories/BribesFactory.sol :: createBribeFlywheel(), flywheelTokens[bribeToken]should be cached in local storage

File: src/gauges/factories/BribesFactory.sol
80:         if (address(flywheelTokens[bribeToken]) != address(0)) revert BribeFlywheelAlreadyExists();
81: 
82:         FlywheelCore flywheel = new FlywheelCore(
83:             bribeToken,
84:             FlywheelBribeRewards(address(0)),
85:             flywheelGaugeWeightBooster,
86:             address(this)
87:         );
88: 
89:         flywheelTokens[bribeToken] = flywheel;

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BribesFactory.sol#L80-L89

8) src/ulysses-amm/UlyssesToken.sol :: addAsset(), assetId[asset] should be cached in local storage

File: src/ulysses-amm/UlyssesToken.sol
45:         if (assetId[asset] != 0) revert AssetAlreadyAdded();
46:         require(ERC20(asset).decimals() == 18);
47:         require(_weight > 0);
48: 
49:         assetId[asset] = assets.length + 1;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesToken.sol#L45-L49

9) src/ulysses-amm/UlyssesToken.sol :: removeAsset(), assetId[asset] should be cached in local storage

File: src/ulysses-amm/UlyssesToken.sol
62:         uint256 assetIndex = assetId[asset] - 1;
63: 
64:         uint256 newAssetsLength = assets.length - 1;
65: 
66:         if (newAssetsLength == 0) revert CannotRemoveLastAsset();
67: 
68:         totalWeights -= weights[assetIndex];
69: 
70:         address lastAsset = assets[newAssetsLength];
71: 
72:         assetId[lastAsset] = assetIndex;
73:         assets[assetIndex] = lastAsset;
74:         weights[assetIndex] = weights[newAssetsLength];
75: 
76:         assets.pop();
77:         weights.pop();
78:         assetId[asset] = 0;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesToken.sol#L62-L78

10) src/gauges/factories/BaseV2GaugeManager.sol :: addGaugeFactory(), activeGaugeFactories[gaugeFactory] should be cached in local storage

File: src/gauges/factories/BaseV2GaugeManager.sol
111:         if (activeGaugeFactories[gaugeFactory]) revert GaugeFactoryAlreadyExists();
112: 
113:         gaugeFactoryIds[gaugeFactory] = gaugeFactories.length;
114:         gaugeFactories.push(gaugeFactory);
115:         activeGaugeFactories[gaugeFactory] = true;

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BaseV2GaugeManager.sol#L111-L115

11) src/gauges/factories/BaseV2GaugeManager.sol :: gaugeFactoryIds[gaugeFactory] , gaugeFactories[gaugeFactoryIds[gaugeFactory]] and activeGaugeFactories[gaugeFactory] should be cached in local storage

File: src/gauges/factories/BaseV2GaugeManager.sol
122:         if (!activeGaugeFactories[gaugeFactory] || gaugeFactories[gaugeFactoryIds[gaugeFactory]] != gaugeFactory) {
123:             revert NotActiveGaugeFactory();
124:         }
125:         delete gaugeFactories[gaugeFactoryIds[gaugeFactory]];
126:         delete gaugeFactoryIds[gaugeFactory];
127:         delete activeGaugeFactories[gaugeFactory];

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BaseV2GaugeManager.sol#L122-L127

12) src/ulysses-omnichain/BranchPort.sol :: initialize(), isBridgeAgentFactory[_bridgeAgentFactory] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
100:         require(coreBranchRouterAddress == address(0), "Contract already initialized");
101:         require(!isBridgeAgentFactory[_bridgeAgentFactory], "Contract already initialized");
102: 
103:         require(_coreBranchRouter != address(0), "CoreBranchRouter is zero address");
104:         require(_bridgeAgentFactory != address(0), "BridgeAgentFactory is zero address");
105: 
106:         coreBranchRouterAddress = _coreBranchRouter;
107:         isBridgeAgentFactory[_bridgeAgentFactory] = true;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L101-L107

13) src/ulysses-omnichain/BranchPort.sol :: _checkTimeLimit(), lastManaged[msg.sender][_token] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
194:         if (block.timestamp - lastManaged[msg.sender][_token] >= 1 days) {
195:             strategyDailyLimitRemaining[msg.sender][_token] = strategyDailyLimitAmount[msg.sender][_token];
196:         }
197:         strategyDailyLimitRemaining[msg.sender][_token] -= _amount;
198:         lastManaged[msg.sender][_token] = block.timestamp;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L194-L198

14) src/ulysses-omnichain/BranchPort.sol :: toggleBridgeAgentFactory(), isBridgeAgentFactory[_newBridgeAgentFactory] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
322:         isBridgeAgentFactory[_newBridgeAgentFactory] = !isBridgeAgentFactory[_newBridgeAgentFactory];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L318

15) src/ulysses-omnichain/BranchPort.sol :: toggleBridgeAgent(), isBridgeAgent[_bridgeAgent] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
329:         isBridgeAgent[_bridgeAgent] = !isBridgeAgent[_bridgeAgent];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L325

16) src/ulysses-omnichain/BranchPort.sol :: toggleStrategyToken(), isStrategyToken[_token] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
347:         isStrategyToken[_token] = !isStrategyToken[_token];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L343

17) src/ulysses-omnichain/BranchPort.sol :: togglePortStrategy(), isPortStrategy[_portStrategy][_token] should be cached in local storage

File: src/ulysses-omnichain/BranchPort.sol
368:         isPortStrategy[_portStrategy][_token] = !isPortStrategy[_portStrategy][_token];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchPort.sol#L364

18) src/ulysses-omnichain/RootPort.sol :: toggleVirtualAccountApproved(), isRouterApproved[_userAccount][_router]

should be cached in local storage

File: src/ulysses-omnichain/RootPort.sol
358:         isRouterApproved[_userAccount][_router] = !isRouterApproved[_userAccount][_router];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L358

19) src/ulysses-omnichain/RootPort.sol :: addBridgeAgent(), isBridgeAgent[_bridgeAgent] should be cached in local storage

File: src/ulysses-omnichain/RootPort.sol
367:         if (isBridgeAgent[_bridgeAgent]) revert AlreadyAddedBridgeAgent();
368: 
369:         bridgeAgents.push(_bridgeAgent);
370:         bridgeAgentsLenght++;
371:         getBridgeAgentManager[_bridgeAgent] = _manager;
372:         isBridgeAgent[_bridgeAgent] = !isBridgeAgent[_bridgeAgent];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L367-L372

20) src/ulysses-omnichain/RootPort.sol :: toggleBridgeAgent(), isBridgeAgent[_bridgeAgent] should be cached in local storage

File: src/ulysses-omnichain/RootPort.sol
400:         isBridgeAgent[_bridgeAgent] = !isBridgeAgent[_bridgeAgent];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L400

21) src/ulysses-omnichain/RootPort.sol :: toggleBridgeAgentFactory(), isBridgeAgentFactory[_bridgeAgentFactory]

should be cached in local storage

File: src/ulysses-omnichain/RootPort.sol 
414:         isBridgeAgentFactory[_bridgeAgentFactory] = !isBridgeAgentFactory[_bridgeAgentFactory];

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L414

22) src/ulysses-omnichain/RootPort.sol :: addEcosystemToken(), isGlobalAddress[_ecoTokenGlobalAddress] should be cached in local storage

File: src/ulysses-omnichain/RootPort.sol
491:         if (isGlobalAddress[_ecoTokenGlobalAddress]) revert AlreadyAddedEcosystemToken();
492:         if (
493:             getUnderlyingTokenFromLocal[_ecoTokenGlobalAddress][localChainId] != address(0)
494:                 || getLocalTokenFromUnder[_ecoTokenGlobalAddress][localChainId] != address(0)
495:         ) revert AlreadyAddedEcosystemToken();
496: 
497:         isGlobalAddress[_ecoTokenGlobalAddress] = true;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootPort.sol#L492-L498

23) src/uni-v3-staker/UniswapV3Staker.sol :: claimReward(), rewards[msg.sender] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
268:         reward = rewards[msg.sender];
269:         if (amountRequested != 0 && amountRequested < reward) {
270:             reward = amountRequested;
271:             rewards[msg.sender] -= reward;
272:         } else {
273:             rewards[msg.sender] = 0;
274:         }

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L263-L268

24) src/uni-v3-staker/UniswapV3Staker.sol :: claimAllRewards(), rewards[msg.sender] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
283:         reward = rewards[msg.sender];
284:         rewards[msg.sender] = 0;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L278-L279

25) src/uni-v3-staker/UniswapV3Staker.sol :: _unstakeToken(), _userAttachements[owner][key.pool] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
406:             // If tokenId is attached to gauge
407:             if (hermesGaugeBoost.isUserGauge(owner, address(gauge)) && _userAttachements[owner][key.pool] == tokenId) {
408:                 // get boost amount and total supply
409:                 (boostAmount, boostTotalSupply) = hermesGaugeBoost.getUserGaugeBoost(owner, address(gauge));
410:                 gauge.detachUser(owner);
411:                 _userAttachements[owner][key.pool] = 0;
412:             }

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L402-L406

26) src/uni-v3-staker/UniswapV3Staker.sol :: _stakeToken(), incentives[incentiveId] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
488:         if (incentives[incentiveId].totalRewardUnclaimed == 0) revert NonExistentIncentiveError();
489: 
490:         if (uint24(tickUpper - tickLower) < poolsMinimumWidth[pool]) revert RangeTooSmallError();
491:         if (liquidity == 0) revert NoLiquidityError();
492: 
493:         stakedIncentiveKey[tokenId] = key;
494: 
495:         // If user not attached to gauge, attach
496:         address tokenOwner = deposits[tokenId].owner;
497:         if (tokenOwner == address(0)) revert TokenNotDeposited();
498: 
499:         UniswapV3Gauge gauge = gauges[pool]; // saves another SLOAD if no tokenId is attached
500: 
501:         if (!hermesGaugeBoost.isUserGauge(tokenOwner, address(gauge))) {
502:             _userAttachements[tokenOwner][pool] = tokenId;
503:             gauge.attachUser(tokenOwner);
504:         }
505: 
506:         deposits[tokenId].stakedTimestamp = uint40(block.timestamp);
507:         incentives[incentiveId].numberOfStakes++;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L483-L502

27) src/uni-v3-staker/UniswapV3Staker.sol :: updateGauges() , gauges[uniswapV3Pool] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
535:         if (address(gauges[uniswapV3Pool]) != uniswapV3Gauge) {
536:             emit GaugeUpdated(uniswapV3Pool, uniswapV3Gauge);
537: 
538:             gauges[uniswapV3Pool] = UniswapV3Gauge(uniswapV3Gauge);

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L531-L534

28) src/uni-v3-staker/UniswapV3Staker.sol :: updateBribeDepot(), bribeDepots[uniswapV3Pool] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
549:         if (newDepot != bribeDepots[uniswapV3Pool]) {
550:             bribeDepots[uniswapV3Pool] = newDepot;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L545-L546

29) src/uni-v3-staker/UniswapV3Staker.sol :: updatePoolMinimumWidth(), poolsMinimumWidth[uniswapV3Pool] should be cached in local storage

File: src/uni-v3-staker/UniswapV3Staker.sol
559:         if (minimumWidth != poolsMinimumWidth[uniswapV3Pool]) {
560:             poolsMinimumWidth[uniswapV3Pool] = minimumWidth;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L555-L556

30) src/ulysses-amm/UlyssesPool.sol :: addNewBandwidth() destinationIds[address(destination)] should be cached in local storage

Here, destinationIds[address(destination)] has been accessed in lines 165 and 207

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesPool.sol#L165-L207

31) src/ulysses-omnichain/RootBridgeAgent.sol :: _payFallbackGas(), getSettlement[_settlementNonce].gasToBridgeOut should be cached in local storage

File: src/ulysses-omnichain/RootBridgeAgent.sol
837:         //Check if sufficient balance
838:         if (minExecCost > getSettlement[_settlementNonce].gasToBridgeOut) {
839:             _forceRevert();
840:             return;
841:         }
842: 
843:         //Update user deposit reverts if not enough gas
844:         getSettlement[_settlementNonce].gasToBridgeOut -= minExecCost.toUint128();

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootBridgeAgent.sol#L839-L845

32) src/ulysses-omnichain/RootBridgeAgent.sol :: anyExecute(), executionHistory[fromChainId][nonce] should be cached in local storage

Here, executionHistory[fromChainId][nonce] has been accessed in lines 919,936,944,961,969,985,993,1009,1017,1045,1053,1080,1088,1115,1123,1138 and 1148

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootBridgeAgent.sol#L919

33) src/ulysses-omnichain/BranchBridgeAgent.sol :: retryDeposit , getDeposit[_depositNonce] should be cached in local storage

Here, getDeposit[_depositNonce] has been accessed in lines 327,332,338,339,340,342,353,354,355,357,365,373,408 and 411

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L327

34) src/ulysses-omnichain/BranchBridgeAgent.sol :: _payFallbackGas, getDeposit[_depositNonce].depositedGas should be cached in local storage

File: src/ulysses-omnichain/BranchBridgeAgent.sol
1066:         //Check if sufficient balance
1067:         if (minExecCost > getDeposit[_depositNonce].depositedGas) {
1068:             _forceRevert();
1069:             return;
1070:         }
1071: 
1072:         //Update user deposit reverts if not enough gas => user must boost deposit with gas
1073:         getDeposit[_depositNonce].depositedGas -= minExecCost.toUint128();

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1069-L1075

35) src/ulysses-omnichain/BranchBridgeAgent.sol :: anyExecute(), executionHistory[nonce] should be cached in local storage

Here, executionHistory[nonce] has been accessed in lines 1148,1162,1170,1186,1194 and 1210

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1148

36) src/gauges/BaseV2Gauge.sol :: addBribeFlywheel(), added[bribeFlywheel] should be cached in local storage

File: src/gauges/BaseV2Gauge.sol
129:         /// @dev Can't add existing flywheel (active or not)
130:         if (added[bribeFlywheel]) revert FlywheelAlreadyAdded();
131: 
132:         address flyWheelRewards = address(bribeFlywheel.flywheelRewards());
133:         FlywheelBribeRewards(flyWheelRewards).setRewardsDepot(multiRewardsDepot);
134: 
135:         multiRewardsDepot.addAsset(flyWheelRewards, bribeFlywheel.rewardToken());
136:         bribeFlywheels.push(bribeFlywheel);
137:         isActive[bribeFlywheel] = true;
138:         added[bribeFlywheel] = true;

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/BaseV2Gauge.sol#L130-L138

37) src/gauges/BaseV2Gauge.sol :: removeBribeFlywheel(), isActive[bribeFlywheel] should be cached in local storage

File: src/gauges/BaseV2Gauge.sol
145:         /// @dev Can only remove active flywheels
146:         if (!isActive[bribeFlywheel]) revert FlywheelNotActive();
147: 
148:         /// @dev This is permanent; can't be re-added
149:         delete isActive[bribeFlywheel];

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/BaseV2Gauge.sol#L146-L149

38) src/gauges/factories/BaseV2GaugeFactory.sol :: createGauge(), strategyGauges[strategy] should be cached in local storage

File: src/gauges/factories/BaseV2GaugeFactory.sol
110:         if (address(strategyGauges[strategy]) != address(0)) revert GaugeAlreadyExists();
111: 
112:         BaseV2Gauge gauge = newGauge(strategy, data);
113:         strategyGauges[strategy] = gauge;

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BaseV2GaugeFactory.sol#L110-L113

39) src/gauges/factories/BaseV2GaugeFactory.sol :: removeGauge, gaugeIds[gauge], gauges[gaugeIds[gauge]] and activeGauges[gauge] should be cached in local storage

File: src/gauges/factories/BaseV2GaugeFactory.sol
131:         if (!activeGauges[gauge] || gauges[gaugeIds[gauge]] != gauge) revert InvalidGauge();
132:         delete gauges[gaugeIds[gauge]];
133:         delete gaugeIds[gauge];
134:         delete activeGauges[gauge];

https://github.com/code-423n4/2023-05-maia/blob/main/src/gauges/factories/BaseV2GaugeFactory.sol#L131-L134

40) src/rewards/base/FlywheelCore.sol :: claimRewards(), rewardsAccrued[user] should be cached in local storage

File: src/rewards/base/FlywheelCore.sol
95:         uint256 accrued = rewardsAccrued[user];
96: 
97:         if (accrued != 0) {
98:             rewardsAccrued[user] = 0;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/base/FlywheelCore.sol#L95-L98

41) src/rewards/base/FlywheelCore.sol :: _addStrategyForRewards(), strategyIndex[strategy] should be cached in local storage

File: src/rewards/base/FlywheelCore.sol
116:         require(strategyIndex[strategy] == 0, "strategy");
117:         strategyIndex[strategy] = ONE;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/base/FlywheelCore.sol#L116-L117

42) src/rewards/base/FlywheelCore.sol :: accrueUser userIndex[strategy][user] and rewardsAccrued[user] should be cached in local storage

File: src/rewards/base/FlywheelCore.sol
183:         uint256 supplierIndex = userIndex[strategy][user];
184: 
185:         // sync user index to global
186:         userIndex[strategy][user] = index;
187: 
188:         // if user hasn't yet accrued rewards, grant them interest from the strategy beginning if they have a balance
189:         // zero balances will have no effect other than syncing to global index
190:         if (supplierIndex == 0) {
191:             supplierIndex = ONE;
192:         }
193: 
194:         uint256 deltaIndex = index - supplierIndex;
195:         // use the booster or token balance to calculate reward balance multiplier
196:         uint256 supplierTokens = address(flywheelBooster) != address(0)
197:             ? flywheelBooster.boostedBalanceOf(strategy, user)
198:             : strategy.balanceOf(user);
199: 
200:         // accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed
201:         uint256 supplierDelta = (supplierTokens * deltaIndex) / ONE;
202:         uint256 supplierAccrued = rewardsAccrued[user] + supplierDelta;
203: 
204:         rewardsAccrued[user] = supplierAccrued;

https://github.com/code-423n4/2023-05-maia/blob/main/src/rewards/base/FlywheelCore.sol#L183-204

43) src/erc-20/ERC20Boost.sol :: attach(), getUserBoost[user] should be cached in local storage

File: src/erc-20/ERC20Boost.sol
126:         if (getUserBoost[user] < userGaugeBoost) {
127:             getUserBoost[user] = userGaugeBoost;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L126-L127

44) src/erc-20/ERC20MultiVotes.sol :: _undelegate(), _delegatesVotesCount[delegator][delegatee] should be cached in local storage

File: src/erc-20/ERC20MultiVotes.sol
218:         uint256 newDelegates = _delegatesVotesCount[delegator][delegatee] - amount;
219: 
220:         if (newDelegates == 0) {
221:             require(_delegates[delegator].remove(delegatee));
222:         }
223: 
224:         _delegatesVotesCount[delegator][delegatee] = newDelegates;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20MultiVotes.sol#L222-L228

45) src/erc-20/ERC20Gauges.sol :: _incrementUserAndGlobalWeights(), getUserWeight[user] should be cached in local storage

File: src/erc-20/ERC20Gauges.sol
232:         newUserWeight = getUserWeight[user] + weight;
233: 
234:         // new user weight must be less than or equal to the total user weight
235:         if (newUserWeight > getVotes(user)) revert OverWeightError();
236: 
237:         // Update gauge state
238:         getUserWeight[user] = newUserWeight;
239: 

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L233-L239

46) src/erc-20/ERC20Gauges.sol :: _decrementGaugeWeight(), getUserGaugeWeight[user][gauge] should be cached in local storage

File: src/erc-20/ERC20Gauges.sol       
295:        uint112 oldWeight = getUserGaugeWeight[user][gauge];
296:
297:        IBaseV2Gauge(gauge).accrueBribes(user);
298:
299:        getUserGaugeWeight[user][gauge] = oldWeight - weight;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L295-L299

47) src/erc-20/ERC20Gauges.sol :: _decrementUserWeights(), getUserWeight[user] should be cached in local storage

File: src/erc-20/ERC20Gauges.sol
317:         newUserWeight = getUserWeight[user] - weight;
318:         getUserWeight[user] = newUserWeight;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Gauges.sol#L317-L318

[G-11] : Use already instantiated storage reference rather than repeatedly fetching the value in a mapping or array

After caching the value of a particular key of a mapping or the value of a particular index of an array,the cache must be used in order to avoid offset calculations which is why the value was cached in the first place.Doing so, ~40 gas savings per access can be saved.

5 instances in 4 files

Estimated gas savings : 5 * 40 = 200

1) src/uni-v3-staker/UniswapV3Staker.sol :: withdrawToken()

File: src/uni-v3-staker/UniswapV3Staker.sol
251:         Deposit storage deposit = deposits[tokenId];
252: 
253:         if (deposit.owner != msg.sender) revert NotCalledByOwner();
254:         if (deposit.stakedTimestamp != 0) revert TokenStakedError();
255: 
256:         delete deposits[tokenId];

recommended code :

251:         Deposit storage deposit = deposits[tokenId];
252: 
253:         if (deposit.owner != msg.sender) revert NotCalledByOwner();
254:         if (deposit.stakedTimestamp != 0) revert TokenStakedError();
255: 
256:         delete deposit;

https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L251

2) src/ulysses-omnichain/RootBridgeAgent.sol :: _redeemSettlement()

File: src/ulysses-omnichain/RootBridgeAgent.sol
592:         Settlement storage settlement = getSettlement[_settlementNonce];
593: 
594:         //Clear Global hTokens To Recipient on Root Chain cancelling Settlement to Branch
595:         for (uint256 i = 0; i < settlement.hTokens.length;) {
596:             //Check if asset
597:             if (settlement.hTokens[i] != address(0)) {
598:                 //Move hTokens from Branch to Root + Mint Sufficient hTokens to match new port deposit
599:                 IPort(localPortAddress).bridgeToRoot(
600:                     msg.sender,
601:                     IPort(localPortAddress).getGlobalTokenFromLocal(settlement.hTokens[i], settlement.toChain),
602:                     settlement.amounts[i],
603:                     settlement.deposits[i],
604:                     settlement.toChain
605:                 );
606:             }
607: 
608:             unchecked {
609:                 ++i;
610:             }
611:         }
612: 
613:         // Delete Settlement
614:         delete getSettlement[_settlementNonce];

recommended code :

File: src/ulysses-omnichain/RootBridgeAgent.sol
592:         Settlement storage settlement = getSettlement[_settlementNonce];
593: 
594:         //Clear Global hTokens To Recipient on Root Chain cancelling Settlement to Branch
595:         for (uint256 i = 0; i < settlement.hTokens.length;) {
596:             //Check if asset
597:             if (settlement.hTokens[i] != address(0)) {
598:                 //Move hTokens from Branch to Root + Mint Sufficient hTokens to match new port deposit
599:                 IPort(localPortAddress).bridgeToRoot(
600:                     msg.sender,
601:                     IPort(localPortAddress).getGlobalTokenFromLocal(settlement.hTokens[i], settlement.toChain),
602:                     settlement.amounts[i],
603:                     settlement.deposits[i],
604:                     settlement.toChain
605:                 );
606:             }
607: 
608:             unchecked {
609:                 ++i;
610:             }
611:         }
612: 
613:         // Delete Settlement
614:         delete settlement;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/RootBridgeAgent.sol#L615

3) src/ulysses-omnichain/BranchBridgeAgent.sol :: _redeemDeposit()

File: src/ulysses-omnichain/BranchBridgeAgent.sol
946:         Deposit storage deposit = getDeposit[_depositNonce];
947: 
948:         //Transfer token to depositor / user
949:         for (uint256 i = 0; i < deposit.hTokens.length;) {
950:             _clearToken(deposit.owner, deposit.hTokens[i], deposit.tokens[i], deposit.amounts[i], deposit.deposits[i]);
951: 
952:             unchecked {
953:                 ++i;
954:             }
955:         }
956: 
957:         //Delete Failed Deposit Token Info
958:         delete getDeposit[_depositNonce];

recommended code :

File: src/ulysses-omnichain/BranchBridgeAgent.sol
946:         Deposit storage deposit = getDeposit[_depositNonce];
947: 
948:         //Transfer token to depositor / user
949:         for (uint256 i = 0; i < deposit.hTokens.length;) {
950:             _clearToken(deposit.owner, deposit.hTokens[i], deposit.tokens[i], deposit.amounts[i], deposit.deposits[i]);
951: 
952:             unchecked {
953:                 ++i;
954:             }
955:         }
956: 
957:         //Delete Failed Deposit Token Info
958:         delete deposit;

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L960

4) src/erc-20/ERC20Boost.sol :: decrementGaugeBoost()

File: src/erc-20/ERC20Boost.sol
177:         GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
178:         if (boost >= gaugeState.userGaugeBoost) {
179:             _userGauges[msg.sender].remove(gauge);
180:             delete getUserGaugeBoost[msg.sender][gauge];

recommended code :

File: src/erc-20/ERC20Boost.sol
177:         GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
178:         if (boost >= gaugeState.userGaugeBoost) {
179:             _userGauges[msg.sender].remove(gauge);
180:             delete gaugeState;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L179

5) src/erc-20/ERC20Boost.sol :: decrementGaugesBoostIndexed()

File: src/erc-20/ERC20Boost.sol
211:             GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
212: 
213:             if (_deprecatedGauges.contains(gauge) || boost >= gaugeState.userGaugeBoost) {
214:                 require(_userGauges[msg.sender].remove(gauge)); // Remove from set. Should never fail.
215:                 delete getUserGaugeBoost[msg.sender][gauge];

recommended code :

File: src/erc-20/ERC20Boost.sol
210: 
211:             GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
212: 
213:             if (_deprecatedGauges.contains(gauge) || boost >= gaugeState.userGaugeBoost) {
214:                 require(_userGauges[msg.sender].remove(gauge)); // Remove from set. Should never fail.
215:                 delete gaugeState;

https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L214

#0 - c4-judge

2023-07-11T07:02:56Z

trust1995 marked the issue as grade-a

#1 - c4-sponsor

2023-07-12T18:29:45Z

0xBugsy marked the issue as sponsor confirmed

#2 - c4-sponsor

2023-07-12T18:29:49Z

0xBugsy marked the issue as disagree with severity

#3 - 0xBugsy

2023-07-12T20:44:41Z

Didn't mean to mark this as marked the issue as disagree with severity! Believe this is a very well structured submission

#4 - 0xLightt

2023-09-07T11:28:42Z

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