Basin - 0xn006e7's results

A composable EVM-native decentralized exchange protocol.

General Information

Platform: Code4rena

Start Date: 03/07/2023

Pot Size: $40,000 USDC

Total HM: 14

Participants: 74

Period: 7 days

Judge: alcueca

Total Solo HM: 9

Id: 259

League: ETH

Basin

Findings Distribution

Researcher Performance

Rank: 31/74

Findings: 1

Award: $58.47

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

58.4732 USDC - $58.47

Labels

bug
G (Gas Optimization)
grade-a
high quality report
sponsor confirmed
G-24

External Links

Table of Constents

S.N.IssuesInstancesGas Savings
G-01Fail as early as possible4-
G-02Use do while loops instead of for loops27896 + additional gas
G-03Cache calldata/memory pointers for complex types to avoid complex offset calculations24310 + additional gas
G-04Avoid caching the result of a function call when that result is being used only once772
G-05Sort array offchain to check duplicates in O(n) instead of O(n^2)1-

[G-01] : Fail as early as possible

Move revert 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 revert statements to the top,users gas can be saved if they call the function and the function reverts as early as possible

4 instances in 1 file

1) src/pumps/MultiFlowPump.sol :: constructor()

File: src/pumps/MultiFlowPump.sol
55:         LOG_MAX_INCREASE = ABDKMathQuad.ONE.add(_maxPercentIncrease).log_2();
56:         // _maxPercentDecrease <= 100%
57:         if (_maxPercentDecrease > ABDKMathQuad.ONE) {
58:             revert InvalidMaxPercentDecreaseArgument(_maxPercentDecrease);
59:         }
60:         LOG_MAX_DECREASE = ABDKMathQuad.ONE.sub(_maxPercentDecrease).log_2();
61:         BLOCK_TIME = _blockTime;
62: 
63:         // ALPHA <= 1
64:         if (_alpha > ABDKMathQuad.ONE) {
65:             revert InvalidAArgument(_alpha);
66:         }

recommended code :

55:         // _maxPercentDecrease <= 100%
56:         if (_maxPercentDecrease > ABDKMathQuad.ONE) {
57:             revert InvalidMaxPercentDecreaseArgument(_maxPercentDecrease);
58:         }
59:         // ALPHA <= 1
60:         if (_alpha > ABDKMathQuad.ONE) {
61:             revert InvalidAArgument(_alpha);
62:         }
63:         LOG_MAX_INCREASE = ABDKMathQuad.ONE.add(_maxPercentIncrease).log_2();
64:         LOG_MAX_DECREASE = ABDKMathQuad.ONE.sub(_maxPercentDecrease).log_2();
65:         BLOCK_TIME = _blockTime;
66:         ALPHA = _alpha;

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L55-L66

2) src/pumps/MultiFlowPump.sol :: readInstantaneousReserves()

File: src/pumps/MultiFlowPump.sol
240:         bytes32 slot = _getSlotForAddress(well);
241:         uint256[] memory reserves = IWell(well).getReserves();
242:         (uint8 numberOfReserves, uint40 lastTimestamp, bytes16[] memory lastReserves) = slot.readLastReserves();
243:         if (numberOfReserves == 0) {
244:             revert NotInitialized();
245:         }

recommended code :

240:         bytes32 slot = _getSlotForAddress(well);
241:         (uint8 numberOfReserves, uint40 lastTimestamp, bytes16[] memory lastReserves) = slot.readLastReserves();
242:         if (numberOfReserves == 0) {
243:             revert NotInitialized();
244:         }
245:         uint256[] memory reserves = IWell(well).getReserves();

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L240-L245

3) src/pumps/MultiFlowPump.sol :: _readCumulativeReserves()

File: src/pumps/MultiFlowPump.sol
285:         bytes32 slot = _getSlotForAddress(well);
286:         uint256[] memory reserves = IWell(well).getReserves();
287:         (uint8 numberOfReserves, uint40 lastTimestamp, bytes16[] memory lastReserves) = slot.readLastReserves();
288:         if (numberOfReserves == 0) {
289:             revert NotInitialized();
290:         }

recommended code :

285:         bytes32 slot = _getSlotForAddress(well);
286:         (uint8 numberOfReserves, uint40 lastTimestamp, bytes16[] memory lastReserves) = slot.readLastReserves();
287:         if (numberOfReserves == 0) {
288:             revert NotInitialized();
289:         }
290:         uint256[] memory reserves = IWell(well).getReserves();

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L286-L291

4) src/pumps/MultiFlowPump.sol :: readTwaReserves()

File: src/pumps/MultiFlowPump.sol
313:         bytes16[] memory byteCumulativeReserves = _readCumulativeReserves(well);
314:         bytes16[] memory byteStartCumulativeReserves = abi.decode(startCumulativeReserves, (bytes16[]));
315:         twaReserves = new uint256[](byteCumulativeReserves.length);
316: 
317:         // Overflow is desired on `startTimestamp`, so SafeCast is not used.
318:         bytes16 deltaTimestamp = _getDeltaTimestamp(uint40(startTimestamp)).fromUInt();
319:         if (deltaTimestamp == bytes16(0)) {
320:             revert NoTimePassed();
321:         }

recommended code :

313:         // Overflow is desired on `startTimestamp`, so SafeCast is not used.
314:         bytes16 deltaTimestamp = _getDeltaTimestamp(uint40(startTimestamp)).fromUInt();
315:         if (deltaTimestamp == bytes16(0)) {
316:             revert NoTimePassed();
317:         }
318:         bytes16[] memory byteCumulativeReserves = _readCumulativeReserves(well);
319:         bytes16[] memory byteStartCumulativeReserves = abi.decode(startCumulativeReserves, (bytes16[]));
320:         twaReserves = new uint256[](byteCumulativeReserves.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L313-L321

[G-02] : Use do while loops instead of for loops

A do while loop costs less gas in comparison to a for loop as the condition is not checked for the first iteration in a do while.

27 instances in 6 files

Note : The gas savings calculated in this section have been calculated via protocol's provided tests.Also,gas savings haven't been calculated for the instances found in libraries as the tests provided by protocol's side for these libraries are fuzzing tests only and thus they don't help in calculating gas savings.But,as it can be analyzed from non-library contracts,gas savings can surely be achieved by applying the recommended mitigation to the for loop instances found in theses libraries as well.

1) src/pumps/MultiFlowPump.sol :: update()

avg. gas saving : 47
Functionminavgmedmaxcalls
Beforeupdate315749921510668352322
Afterupdate313249874510118346822
File: src/pumps/MultiFlowPump.sol
83:             for (uint256 i; i < numberOfReserves; ++i) {
84:                 // If a reserve is 0, then the pump cannot be initialized.
85:                 if (reserves[i] == 0) return;
86:             }

...

116:         for (uint256 i; i < numberOfReserves; ++i) {
117:             // Use a minimum of 1 for reserve. Geometric means will be set to 0 if a reserve is 0.
118:             pumpState.lastReserves[i] = _capReserve(
119:                 pumpState.lastReserves[i], (reserves[i] > 0 ? reserves[i] : 1).fromUIntToLog2(), blocksPassed
120:             );
121:             pumpState.emaReserves[i] =
122:                 pumpState.lastReserves[i].mul((ABDKMathQuad.ONE.sub(alphaN))).add(pumpState.emaReserves[i].mul(alphaN));
123:             pumpState.cumulativeReserves[i] =
124:                 pumpState.cumulativeReserves[i].add(pumpState.lastReserves[i].mul(deltaTimestampBytes));
125:         }

recommended code :

84:             uint256 i;
85:             do {
86:                 // If a reserve is 0, then the pump cannot be initialized.
87:                 if (reserves[i] == 0) return;
88                  ++i;
89:             } while (i < numberOfReserves);

...

116:         uint256 j;
117:         do {
118:             // Use a minimum of 1 for reserve. Geometric means will be set to 0 if a reserve is 0.
119:             pumpState.lastReserves[j] = _capReserve(
120:                 pumpState.lastReserves[j], (reserves[j] > 0 ? reserves[j] : 1).fromUIntToLog2(), blocksPassed
121:             );
122:             pumpState.emaReserves[j] =
123:                 pumpState.lastReserves[j].mul((ABDKMathQuad.ONE.sub(alphaN))).add(pumpState.emaReserves[j].mul(alphaN));
124:             pumpState.cumulativeReserves[j] =
125:                 pumpState.cumulativeReserves[j].add(pumpState.lastReserves[j].mul(deltaTimestampBytes));
126:             ++j;
127:         } while(j < numberOfReserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L84-L87

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L116-L125

2) src/pumps/MultiFlowPump.sol :: _init(), this function gas saving has been calculated by using update function as it is an internal function used by update

avg. gas saving : 15
Functionminavgmedmaxcalls
Beforeupdate315749921510668352322
Afterupdate315749906510668352322
File: src/pumps/MultiFlowPump.sol
152:         for (uint256 i; i < numberOfReserves; ++i) {
153:             if (reserves[i] == 0) return;
154:             byteReserves[i] = reserves[i].fromUIntToLog2();
155:         }

recommended code :

152:         uint256 i;
153:         do {
154:             if (reserves[i] == 0) return;
155:             byteReserves[i] = reserves[i].fromUIntToLog2();
156:             ++i;
157:         } while (i < numberOfReserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L152-L155

3) src/pumps/MultiFlowPump.sol :: readLastReserves()

avg. gas saving : 61
Functionminavgmedmaxcalls
BeforereadLastReserves114261145411454114824
AfterreadLastReserves113651139311393114214
File: src/pumps/MultiFlowPump.sol
177:         for (uint256 i; i < numberOfReserves; ++i) {
178:             reserves[i] = bytesReserves[i].pow_2ToUInt();
179:         }

recommended code :

177:         uint256 i;
178:         do {
179:             reserves[i] = bytesReserves[i].pow_2ToUInt();
180:             ++i;
181:         } while (i < numberOfReserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L178-L180

4) src/pumps/MultiFlowPump.sol :: readLastInstantaneousReserves()

avg. gas saving : 61
Functionminavgmedmaxcalls
BeforereadLastInstantaneousReserves120631268212196142734
AfterreadLastInstantaneousReserves120021262112135142124
File: src/pumps/MultiFlowPump.sol
234:         for (uint256 i; i < numberOfReserves; ++i) {
235:             reserves[i] = byteReserves[i].pow_2ToUInt();
236:         }

recommended code :

234:         uint256 i;
235:         do {
236:             reserves[i] = byteReserves[i].pow_2ToUInt();
237:             ++i;
238:         } while (i < numberOfReserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L234-L236

5) src/pumps/MultiFlowPump.sol :: readInstantaneousReserves()

avg. gas saving : 61
Functionminavgmedmaxcalls
BeforereadInstantaneousReserves575706029857773655516
AfterreadInstantaneousReserves575096023757712654906
File: src/pumps/MultiFlowPump.sol
254:         for (uint256 i; i < numberOfReserves; ++i) {
255:             lastReserves[i] = _capReserve(lastReserves[i], reserves[i].fromUIntToLog2(), blocksPassed);
256:             emaReserves[i] =
257:                 lastReserves[i].mul((ABDKMathQuad.ONE.sub(alphaN))).add(lastEmaReserves[i].mul(alphaN)).pow_2ToUInt();
258:         }

recommended code :

254:         uint256 i;
255:         do {
256:             lastReserves[i] = _capReserve(lastReserves[i], reserves[i].fromUIntToLog2(), blocksPassed);
257:             emaReserves[i] =
258:                 lastReserves[i].mul((ABDKMathQuad.ONE.sub(alphaN))).add(lastEmaReserves[i].mul(alphaN)).pow_2ToUInt();
259:             ++i;
260:         } while (i < numberOfReserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L255-L259

6) src/pumps/MultiFlowPump.sol :: _readCumulativeReserves(), this function gas saving has been calculated by using readCumulativeReserves function as it is an internal function used by readCumulativeReserves

avg. gas saving : 58
Functionminavgmedmaxcalls
BeforereadCumulativeReserves4486851151482315738114
AfterreadCumulativeReserves4481051093481735732314
File: src/pumps/MultiFlowPump.sol
299:         for (uint256 i; i < cumulativeReserves.length; ++i) {
300:             lastReserves[i] = _capReserve(lastReserves[i], reserves[i].fromUIntToLog2(), blocksPassed);
301:             cumulativeReserves[i] = cumulativeReserves[i].add(lastReserves[i].mul(deltaTimestampBytes));
302:         }

recommended code :

299:         uint256 i;
300:         do {
301:             lastReserves[i] = _capReserve(lastReserves[i], reserves[i].fromUIntToLog2(), blocksPassed);
302:             cumulativeReserves[i] = cumulativeReserves[i].add(lastReserves[i].mul(deltaTimestampBytes));
303:             ++i;
304:         } while (i < cumulativeReserves.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L301-L304

7) src/pumps/MultiFlowPump.sol :: readTwaReserves()

avg. gas saving : 43
Functionminavgmedmaxcalls
BeforereadTwaReserves453365807960889652558
AfterreadTwaReserves453365803660831651978
File: src/pumps/MultiFlowPump.sol
322:         for (uint256 i; i < byteCumulativeReserves.length; ++i) {
323:             // Currently, there is no support for overflow.
324:             twaReserves[i] =
325:                 (byteCumulativeReserves[i].sub(byteStartCumulativeReserves[i])).div(deltaTimestamp).pow_2ToUInt();
326:         }

recommended code :

322:         uint256 i;
323:         do {
324:             // Currently, there is no support for overflow.
325:             twaReserves[i] =
326:                 (byteCumulativeReserves[i].sub(byteStartCumulativeReserves[i])).div(deltaTimestamp).pow_2ToUInt();
327:             ++i;
328:         } while (byteCumulativeReserves.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L322-L326

8) src/Well.sol :: shift()

avg. gas saving : 58
Functionminavgmedmaxcalls
Beforeshift140992936530336426914
Aftershift140392930730278426334
File: src/Well.sol
363:         for (uint256 i; i < _tokens.length; ++i) {
364:             reserves[i] = _tokens[i].balanceOf(address(this));
365:         }

recommended code :

363:         uint256 i;
364:         do {
365:             reserves[i] = _tokens[i].balanceOf(address(this));
366:             ++i;
367:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L363-L365

9) src/Well.sol :: getShiftOut()

avg. gas saving : 58
Functionminavgmedmaxcalls
BeforegetShiftOut111501128911359113593
AftergetShiftOut110921123111301113013
File: src/Well.sol
382:         for (uint256 i; i < _tokens.length; ++i) {
383:             reserves[i] = _tokens[i].balanceOf(address(this));
384:         }

recommended code :

382:         uint256 i;
383:         do {
384:             reserves[i] = _tokens[i].balanceOf(address(this));
385:             ++i;
386:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L382-L384

10) src/Well.sol :: _addLiquidity(), this function gas saving has been calculated by using addLiquidity function as it is an internal function used by addLiquidity

avg. gas saving : 44
Functionminavgmedmaxcalls
BeforeaddLiquidity1315921315921315921315924
AfteraddLiquidity1315481315481315481315484
File: src/Well.sol
422:         if (feeOnTransfer) {
423:             for (uint256 i; i < _tokens.length; ++i) {
424:                 if (tokenAmountsIn[i] == 0) continue;
425:                 tokenAmountsIn[i] = _safeTransferFromFeeOnTransfer(_tokens[i], msg.sender, tokenAmountsIn[i]);
426:                 reserves[i] = reserves[i] + tokenAmountsIn[i];
427:             }
428:         } else {
429:             for (uint256 i; i < _tokens.length; ++i) {
430:                 if (tokenAmountsIn[i] == 0) continue;
431:                 _tokens[i].safeTransferFrom(msg.sender, address(this), tokenAmountsIn[i]);
432:                 reserves[i] = reserves[i] + tokenAmountsIn[i];
433:             }
434:         }

recommended code :

422:         if (feeOnTransfer) {
423:             uint256 i;
424:             do {
425:                 if (tokenAmountsIn[i] == 0) continue;
426:                 tokenAmountsIn[i] = _safeTransferFromFeeOnTransfer(_tokens[i], msg.sender, tokenAmountsIn[i]);
427:                 reserves[i] = reserves[i] + tokenAmountsIn[i];
428:                 ++i;
429:             } while (i < _tokens.length);
430:         } else {
431:             uint256 i;
432:             do {
433:                 if (tokenAmountsIn[i] == 0) continue;
434:                 _tokens[i].safeTransferFrom(msg.sender, address(this), tokenAmountsIn[i]);
435:                 reserves[i] = reserves[i] + tokenAmountsIn[i];
436:                 ++i;
437:             } while (i < _tokens.length);
438:         }

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L422-L434

11) src/Well.sol :: removeLiquidity()

avg. gas saving : 40
Functionminavgmedmaxcalls
BeforeremoveLiquidity57975844876460893605
AfterremoveLiquidity57975840876402893025
File: src/Well.sol
473:         for (uint256 i; i < _tokens.length; ++i) {
474:             if (tokenAmountsOut[i] < minTokenAmountsOut[i]) {
475:                 revert SlippageOut(tokenAmountsOut[i], minTokenAmountsOut[i]);
476:             }
477:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
478:             reserves[i] = reserves[i] - tokenAmountsOut[i];
479:         }

recommended code :

473:         uint256 i;
474:         do {
475:             if (tokenAmountsOut[i] < minTokenAmountsOut[i]) {
476:                 revert SlippageOut(tokenAmountsOut[i], minTokenAmountsOut[i]);
477:             }
478:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
479:             reserves[i] = reserves[i] - tokenAmountsOut[i];
480:             ++i;
481:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L473-L479

12) src/Well.sol :: removeLiquidityImbalanced()

avg. gas saving : 47
Functionminavgmedmaxcalls
BeforeremoveLiquidityImbalanced57747264289489979895
AfterremoveLiquidityImbalanced57747259589431979315
File: src/Well.sol
557:         for (uint256 i; i < _tokens.length; ++i) {
558:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
559:             reserves[i] = reserves[i] - tokenAmountsOut[i];
560:         }

recommended code :

554:         uint256 i;
555:         do {
556:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
557:             reserves[i] = reserves[i] - tokenAmountsOut[i];
558:             ++i;
559:         } while (_tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L557-L560

13) src/Well.sol :: getRemoveLiquidityImbalancedIn()

avg. gas saving : 59
Functionminavgmedmaxcalls
BeforegetRemoveLiquidityImbalancedIn68361050211336133363
AftergetRemoveLiquidityImbalancedIn67771044311277132773
File: src/Well.sol
579:         for (uint256 i; i < _tokens.length; ++i) {
580:             reserves[i] = reserves[i] - tokenAmountsOut[i];
581:         }

recommended code :

579:         uint256 i;
580:         do {
581:             reserves[i] = reserves[i] - tokenAmountsOut[i];
582:             ++i;
583:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L579-L581

14) src/Well.sol :: sync()

avg. gas saving : 58
Functionminavgmedmaxcalls
Beforesync160071600716007160073
Aftersync159491594915949159493
File: src/Well.sol
593:         for (uint256 i; i < _tokens.length; ++i) {
594:             reserves[i] = _tokens[i].balanceOf(address(this));
595:         }

recommended code :

593:         uint256 i;
594:         do {
595:             reserves[i] = _tokens[i].balanceOf(address(this));
596:             ++i;
597:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L593-L595

15) src/Well.sol :: skim()

avg. gas saving : 58
Functionminavgmedmaxcalls
Beforeskim530285302853028530281
Afterskim529705297052970529701
File: src/Well.sol
609:         for (uint256 i; i < _tokens.length; ++i) {
610:             skimAmounts[i] = _tokens[i].balanceOf(address(this)) - reserves[i];
611:             if (skimAmounts[i] > 0) {
612:                 _tokens[i].safeTransfer(recipient, skimAmounts[i]);
613:             }
614:         }

recommended code :

609:         uint256 i;
610:         do {
611:             skimAmounts[i] = _tokens[i].balanceOf(address(this)) - reserves[i];
612:             if (skimAmounts[i] > 0) {
613:                 _tokens[i].safeTransfer(recipient, skimAmounts[i]);
614:             }
615:             ++i;
616:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L607-L612

16) src/Well.sol :: _setReserves(), this function gas saving has been calculated by using sync function as it is an internal function used by sync

avg. gas saving : 58
Functionminavgmedmaxcalls
Beforesync160071600716007160073
Aftersync159491594915949159493
File: src/Well.sol
633:         for (uint256 i; i < reserves.length; ++i) {
634:             if (reserves[i] > _tokens[i].balanceOf(address(this))) revert InvalidReserves();
635:         }

recommended code :

633:         uint256 i;
634:         do {
635:             if (reserves[i] > _tokens[i].balanceOf(address(this))) revert InvalidReserves();
636:             ++i:
637:         } while (i < reserves.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L633-L635

17)src/Well.sol :: _getIJ(), this function gas saving has been calculated by using swapTo function as it is an internal function used by swapTo

avg. gas saving : 46
Functionminavgmedmaxcalls
BeforeswapTo57713340127108694806
AfterswapTo57713335527056694226
File: src/Well.sol
738:         for (uint256 k; k < _tokens.length; ++k) {
739:             if (iToken == _tokens[k]) {
740:                 i = k;
741:                 foundI = true;
742:             } else if (jToken == _tokens[k]) {
743:                 j = k;
744:                 foundJ = true;
745:             }
746:         }

recommended code :

738:         uint256 k;
739:         do {
740:             if (iToken == _tokens[k]) {
741:                 i = k;
742:                 foundI = true;
743:             } else if (jToken == _tokens[k]) {
744:                 j = k;
745:                 foundJ = true;
746:             }
747:             ++k;
748:         } while(k < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L738-L746

18) src/Well.sol :: _getJ(), this function gas saving has been calculated by using shift function as it is an internal function used by shift

avg. gas saving : 24
Functionminavgmedmaxcalls
Beforeshift140992936530336426914
Aftershift140712934130316426634
File: src/Well.sol
760:         for (j; j < _tokens.length; ++j) {
761:             if (jToken == _tokens[j]) {
762:                 return j;
763:             }
764:         }

recommended code :

760:         uint256 k;
761:         do {
762:             if (jToken == _tokens[k]) {
763:                 return k;
764:             }
765:             ++k;
766:         } while (k < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L760-L764

19) src/libraries/LibBytes.sol :: readUint128()

File: src/libraries/LibBytes.sol
092:         for (uint256 i = 1; i <= n; ++i) {
093:             // `iByte` is the byte position for the current slot:
094:             // i        1 2 3 4 5 6
095:             // iByte    0 0 1 1 2 2
096:             iByte = (i - 1) / 2 * 32;
097:             // Equivalent to "i % 2 == 1", but cheaper.
098:             if (i & 1 == 1) {
099:                 assembly {
100:                     mstore(
101:                         // store at index i * 32; i = 0 is skipped by loop
102:                         add(reserves, mul(i, 32)),
103:                         shr(128, shl(128, sload(add(slot, iByte))))
104:                     )
105:                 }
106:             } else {
107:                 assembly {
108:                     mstore(add(reserves, mul(i, 32)), shr(128, sload(add(slot, iByte))))
109:                 }
110:             }
111:         }

recommended code :

093:         do {
094:             // `iByte` is the byte position for the current slot:
095:             // i        1 2 3 4 5 6
096:             // iByte    0 0 1 1 2 2
097:             iByte = (i - 1) / 2 * 32;
098:             // Equivalent to "i % 2 == 1", but cheaper.
099:             if (i & 1 == 1) {
100:                 assembly {
101:                     mstore(
102:                         // store at index i * 32; i = 0 is skipped by loop
103:                         add(reserves, mul(i, 32)),
104:                         shr(128, shl(128, sload(add(slot, iByte))))
105:                     )
106:                 }
107:             } else {
108:                 assembly {
109:                     mstore(add(reserves, mul(i, 32)), shr(128, sload(add(slot, iByte))))
110:                 }
111:             }
112:         ++i;
113:         } while (i <= n);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibBytes.sol#L92-L111

20) src/libraries/LibBytes16.sol :: storeBytes16()

File: src/libraries/LibBytes16.sol
28:             for (uint256 i; i < maxI; ++i) {
29:                 iByte = i * 64;
30:                 assembly {
31:                     sstore(
32:                         add(slot, mul(i, 32)),
33:                         add(mload(add(reserves, add(iByte, 32))), shr(128, mload(add(reserves, add(iByte, 64)))))
34:                     )
35:                 }
36:             }

recommended code :

28:             uint256 i;
29:             do {
30:                 iByte = i * 64;
31:                 assembly {
32:                     sstore(
33:                         add(slot, mul(i, 32)),
34:                         add(mload(add(reserves, add(iByte, 32))), shr(128, mload(add(reserves, add(iByte, 64)))))
35:                     )
36:                 }
37:             ++i;
38:             } while (i < maxI);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibBytes16.sol#L28-L36

21) src/libraries/LibBytes16.sol :: readBytes16()

File: src/libraries/LibBytes16.sol
69:         for (uint256 i = 1; i <= n; ++i) {
70:             // `iByte` is the byte position for the current slot:
71:             // i        1 2 3 4 5 6
72:             // iByte    0 0 1 1 2 2
73:             iByte = (i - 1) / 2 * 32;
74:             // Equivalent to "i % 2 == 1" but cheaper.
75:             if (i & 1 == 1) {
76:                 assembly {
77:                     mstore(
78:                         // store at index i * 32; i = 0 is skipped by loop
79:                         add(reserves, mul(i, 32)),
80:                         sload(add(slot, iByte))
81:                     )
82:                 }
83:             } else {
84:                 assembly {
85:                     mstore(add(reserves, mul(i, 32)), shl(128, sload(add(slot, iByte))))
86:                 }
87:             }
88:         }

recommended code :

69:         uint256 i; 
70:         do {
71:             // `iByte` is the byte position for the current slot:
72:             // i        1 2 3 4 5 6
73:             // iByte    0 0 1 1 2 2
74:             iByte = (i - 1) / 2 * 32;
75:             // Equivalent to "i % 2 == 1" but cheaper.
76:             if (i & 1 == 1) {
77:                 assembly {
78:                     mstore(
79:                         // store at index i * 32; i = 0 is skipped by loop
80:                         add(reserves, mul(i, 32)),
81:                         sload(add(slot, iByte))
82:                     )
83:                 }
84:             } else {
85:                 assembly {
86:                     mstore(add(reserves, mul(i, 32)), shl(128, sload(add(slot, iByte))))
87:                 }
88:             }
89:         ++i;
90:         } while (i <= n);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibBytes16.sol#L69-L88

22) src/libraries/LibLastReserveBytes.sol :: storeLastReserves()

File: src/libraries/LibLastReserveBytes.sol
41:             for (uint256 i = 1; i < maxI; ++i) {
42:                 iByte = i * 64;
43:                 assembly {
44:                     sstore(
45:                         add(slot, mul(i, 32)),
46:                         add(mload(add(reserves, add(iByte, 32))), shr(128, mload(add(reserves, add(iByte, 64)))))
47:                     )
48:                 }
49:             }

recommended code :

41:             uint256 i;
42:             do {
43:                 iByte = i * 64;
44:                 assembly {
45:                     sstore(
46:                         add(slot, mul(i, 32)),
47:                         add(mload(add(reserves, add(iByte, 32))), shr(128, mload(add(reserves, add(iByte, 64)))))
48:                     )
49:                 }
50:             ++i;
51:             } while (i < maxI);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibLastReserveBytes.sol#L41-L49

23) src/libraries/LibLastReserveBytes.sol :: readLastReserves()

File: src/libraries/LibLastReserveBytes.sol
092:             for (uint256 i = 3; i <= n; ++i) {
093:                 // `iByte` is the byte position for the current slot:
094:                 // i        3 4 5 6
095:                 // iByte    1 1 2 2
096:                 iByte = (i - 1) / 2 * 32;
097:                 // Equivalent to "i % 2 == 1" but cheaper.
098:                 if (i & 1 == 1) {
099:                     assembly {
100:                         mstore(
101:                             // store at index i * 32; i = 0 is skipped by loop
102:                             add(reserves, mul(i, 32)),
103:                             sload(add(slot, iByte))
104:                         )
105:                     }
106:                 } else {
107:                     assembly {
108:                         mstore(add(reserves, mul(i, 32)), shl(128, sload(add(slot, iByte))))
109:                     }
110:                 }
111:             }

recommended code :

092:             uint256 i = 3;
093:             do {
094:                 // `iByte` is the byte position for the current slot:
095:                 // i        3 4 5 6
096:                 // iByte    1 1 2 2
097:                 iByte = (i - 1) / 2 * 32;
098:                 // Equivalent to "i % 2 == 1" but cheaper.
099:                 if (i & 1 == 1) {
100:                     assembly {
101:                         mstore(
102:                             // store at index i * 32; i = 0 is skipped by loop
103:                             add(reserves, mul(i, 32)),
104:                             sload(add(slot, iByte))
105:                         )
106:                     }
107:                 } else {
108:                     assembly {
109:                         mstore(add(reserves, mul(i, 32)), shl(128, sload(add(slot, iByte))))
110:                     }
111:                 }
112:             ++i;
113:             } while (i <= n);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibLastReserveBytes.sol#L93-L112

24) src/libraries/LibWellConstructor.sol :: encodeWellImmutableData()

File: src/libraries/LibWellConstructor.sol
43:         for (uint256 i; i < _pumps.length; ++i) {
44:             packedPumps = abi.encodePacked(
45:                 packedPumps,            // previously packed pumps
46:                 _pumps[i].target,       // pump address
47:                 _pumps[i].data.length,  // pump data length
48:                 _pumps[i].data          // pump data (bytes)
49:             );
50:         }

recommended code :

43:         uint256 i;
44:         do {
45:             packedPumps = abi.encodePacked(
46:                 packedPumps,            // previously packed pumps
47:                 _pumps[i].target,       // pump address
48:                 _pumps[i].data.length,  // pump data length
49:                 _pumps[i].data          // pump data (bytes)
50:             );
51:         ++i;
52:         } while (i < _pumps.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibWellConstructor.sol#L43-L50

25) src/libraries/LibWellConstructor.sol :: encodeWellInitFunctionCall()

File: src/libraries/LibWellConstructor.sol
73:         for (uint256 i = 1; i < _tokens.length; ++i) {
74:             name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i])));
75:             symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i])));
76:         }

recommended code :

73:         uint256 i = 1;
74:         do {
75:             name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i])));
76:             symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i])));
77:             ++i;
78:         } while (i < _tokens.length);

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibWellConstructor.sol#L73-L76

[G-03] : 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.

24 instances in 3 files

Note : The gas savings calculated in this section have been calculated via protocol's provided tests.Also,gas savings haven't been calculated for the instances found in libraries as the tests provided by protocol's side for these libraries are fuzzing tests only and thus they don't help in calculating gas savings.But,as it can be analyzed from non-library contracts,gas savings can surely be achieved by applying the recommended mitigation to the instances found in theses libraries as well.

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

1) src/pumps/MultiFlowPump.sol :: update(), cache reserves[i]

avg. gas saving : 32
Functionminavgmedmaxcalls
Beforeupdate315749921510668352322
Afterupdate315749889509968345322
File: src/pumps/MultiFlowPump.sol
118:             pumpState.lastReserves[i] = _capReserve(
119:                 pumpState.lastReserves[i], (reserves[i] > 0 ? reserves[i] : 1).fromUIntToLog2(), blocksPassed
120:             );

recommended code :

118:             uint256 _reserve = reserves[i];
119:             pumpState.lastReserves[i] = _capReserve(
120:                 pumpState.lastReserves[i], (_reserve > 0 ? _reserve : 1).fromUIntToLog2(), blocksPassed
121:             );

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L118-L120

2) src/pumps/MultiFlowPump.sol :: _init(), cache reserves[i], this function gas saving has been calculated by using update function as it is an internal function used by update

avg. gas saving : 20
Functionminavgmedmaxcalls
Beforeupdate315749921510668352322
Afterupdate315749901510668352322
File: src/pumps/MultiFlowPump.sol
153:             if (reserves[i] == 0) return;
154:             byteReserves[i] = reserves[i].fromUIntToLog2();

recommended code :

153:             uint256 _reserve = reserves[i];
154:             if (_reserve == 0) return;
155:             byteReserves[i] = _reserve.fromUIntToLog2();

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L153-L154

3) src/Well.sol :: init(), cache _tokens[i]

No test has been provided for this function from ptotocol's side(using which gas savings could be calculated) but as it can be analyzed from similar instances in this section,gas savings can surely be achieved by applying the recommended mitigation to this instance also.

File: src/Well.sol
38:                 if (_tokens[i] == _tokens[j]) {
39:                     revert DuplicateTokens(_tokens[i]);
40:                 }

recommended code :

38:                 address _token = _tokens[i];
39:                 if (_token == _tokens[j]) {
40:                     revert DuplicateTokens(_token);
41:                 }

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L38-L39

4) src/Well.sol :: _addLiquidity(), cache tokenAmountsIn[i], this function gas saving has been calculated by using addLiquidity function as it is an internal function used by addLiquidity

avg. gas saving : 140
Functionminavgmedmaxcalls
BeforeaddLiquidity1315921315921315921315924
AfteraddLiquidity1314521314521314521314524
File: src/Well.sol
430:                 if (tokenAmountsIn[i] == 0) continue;
431:                 _tokens[i].safeTransferFrom(msg.sender, address(this), tokenAmountsIn[i]);
432:                 reserves[i] = reserves[i] + tokenAmountsIn[i];

recommended code :

430:                 uint256 _tokenAmountIn = tokenAmountsIn[i];
431:                 if (_tokenAmountIn == 0) continue;
432:                 _tokens[i].safeTransferFrom(msg.sender, address(this), _tokenAmountIn);
433:                 reserves[i] = reserves[i] + _tokenAmountIn;

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L430-L432

5) src/Well.sol :: removeLiquidity(), cache tokenAmountsOut[i] and minTokenAmountsOut[i]

avg. gas saving : 62
Functionminavgmedmaxcalls
BeforeremoveLiquidity57975044836660893605
AfterremoveLiquidity57975038636518892185
File: src/Well.sol
474:             if (tokenAmountsOut[i] < minTokenAmountsOut[i]) {
475:                 revert SlippageOut(tokenAmountsOut[i], minTokenAmountsOut[i]);
476:             }
477:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
478:             reserves[i] = reserves[i] - tokenAmountsOut[i];

recommended code :

474:             uint256 _tokenAmountOut = tokenAmountsOut[i];
475:             uint256 _minTokenAmountOut = minTokenAmountsOut[i];
476:             if (_tokenAmountOut < _minTokenAmountOut) {
477:                 revert SlippageOut(_tokenAmountOut, _minTokenAmountOut);
478:             }
479:             _tokens[i].safeTransfer(recipient, _tokenAmountOut);
480:             reserves[i] = reserves[i] - _tokenAmountOut;

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L474-L478

6) src/Well.sol :: removeLiquidityImbalanced(), cache tokenAmountsOut[i]

avg. gas saving : 56
Functionminavgmedmaxcalls
BeforeremoveLiquidityImbalanced57747264289489979895
AfterremoveLiquidityImbalanced57747258689419979195
File: src/Well.sol
558:             _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]);
559:             reserves[i] = reserves[i] - tokenAmountsOut[i];

recommended code :

558:             uint256 _tokenAmountOut = tokenAmountsOut[i];
559:             _tokens[i].safeTransfer(recipient, _tokenAmountOut);
560:             reserves[i] = reserves[i] - _tokenAmountOut;

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L558-L559

7) src/libraries/LibWellConstructor.sol :: encodeWellImmutableData(), cache calldata pointer for _pumps[i] and also _pumps[i].data in memory

File: src/libraries/LibWellConstructor.sol
44:             packedPumps = abi.encodePacked(
45:                 packedPumps,            // previously packed pumps
46:                 _pumps[i].target,       // pump address
47:                 _pumps[i].data.length,  // pump data length
48:                 _pumps[i].data          // pump data (bytes)
49:             );

recommended code :

44:             Call memory _pump = _pumps[i];
45:             bytes memory _data = _pump.data;
46:             packedPumps = abi.encodePacked(
47:                 packedPumps,            // previously packed pumps
48:                 _pump.target,       // pump address
49:                 _pump._data.length,  // pump data length
50:                 _pump._data          // pump data (bytes)
51:             );

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibWellConstructor.sol#L44-L49

8) src/libraries/LibWellConstructor.sol :: encodeWellInitFunctionCall(), cache _tokens[i]

File: src/libraries/LibWellConstructor.sol
74:             name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i])));
75:             symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i])));

recommended code :

74:             IERC20 _token = _tokens[i];
75:             name = string.concat(name, ":", LibContractInfo.getSymbol(address(_token)));
76:             symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_token)));

https://github.com/code-423n4/2023-07-basin/blob/main/src/libraries/LibWellConstructor.sol#L74-L75

[G-04] : 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.

7 instances in 2 files

Note : The gas savings calculated in this section have been calculated via protocol's provided tests.

1) src/pumps/MultiFlowPump.sol :: readLastReserves()

Here, slot cached in line 171 has been used only once in line 172.

avg. gas saving : 5
Functionminavgmedmaxcalls
BeforereadLastReserves114261145411454114824
AfterreadLastReserves114211144911449114774
File: src/pumps/MultiFlowPump.sol
171:         bytes32 slot = _getSlotForAddress(well);
172:         (uint8 numberOfReserves,, bytes16[] memory bytesReserves) = slot.readLastReserves();

recommended code :

172:         (uint8 numberOfReserves,, bytes16[] memory bytesReserves) = _getSlotForAddress(well).readLastReserves();

https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L172-L173

2) src/Well.sol :: wellFunction()

Here, dataLoc cached in line 91 has been used only once in line 92

avg. gas saving : 13
Functionminavgmedmaxcalls
BeforewellFunction20492049204920499
AfterwellFunction20362036203620369
File: src/Well.sol
91:         uint256 dataLoc = LOC_VARIABLE + numberOfTokens() * ONE_WORD;
92:         _wellFunction.data = _getArgBytes(dataLoc, wellFunctionDataLength());

recommended code :

91:         _wellFunction.data = _getArgBytes(LOC_VARIABLE + numberOfTokens() * ONE_WORD, wellFunctionDataLength());

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L90-L91

3) src/Well.sol :: firstPump(), this function gas saving has been calculated by using removeLiquidity function as it is an internal function used by removeLiquidity

Here, pumpDataLenght cached in line 176 has been used only once in line 177

avg. gas saving : 15
Functionminavgmedmaxcalls
BeforeremoveLiquidity57975844876460893605
AfterremoveLiquidity57975843376441893415
File: src/Well.sol
176:         uint256 pumpDataLength = _getArgUint256(dataLoc + PACKED_ADDRESS);
177:         _pump.data = _getArgBytes(dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, pumpDataLength);

recommended code :

176:         _pump.data = _getArgBytes(dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, _getArgUint256(dataLoc + PACKED_ADDRESS));

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L176-L177

4) src/Well.sol :: getRemoveLiquidityOut()

Here, lpTokenSupply cached in line 488 has been used only once in line 490

avg. gas saving : 13
Functionminavgmedmaxcalls
BeforegetRemoveLiquidityOut71481039810398136482
AftergetRemoveLiquidityOut71351038510385136352
File: src/Well.sol
488:         uint256 lpTokenSupply = totalSupply();
489: 
490:         tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, lpTokenSupply);

recommended code :

489:         tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, totalSupply());

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L488-L490

5) src/Well.sol :: getRemoveLiquidityOneTokenOut()

Here, j cached in line 525 has been used only once in line 526

avg. gas saving : 13
Functionminavgmedmaxcalls
BeforegetRemoveLiquidityOneTokenOut133211332113321133212
AftergetRemoveLiquidityOneTokenOut133081330813308133082
File: src/Well.sol
525:         uint256 j = _getJ(_tokens, tokenOut);
526:         tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, j, reserves);

recommended code :

525:         tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, _getJ(_tokens, tokenOut), reserves);

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L525-L526

6) src/Well.sol :: _getRemoveLiquidityOneTokenOut(), this function gas saving has been calculated by using getRemoveLiquidityOneTokenOut function as it is an internal function used by getRemoveLiquidityOneTokenOut

Here, newLpTokenSupply cached in line 541 has been used only once in line 542 and also newReserveJ cached in line 542 has been used only once in line 543

avg. gas saving : 13
Functionminavgmedmaxcalls
BeforegetRemoveLiquidityOneTokenOut133211332113321133212
AftergetRemoveLiquidityOneTokenOut133081330813308133082
File: src/Well.sol
541:         uint256 newLpTokenSupply = totalSupply() - lpAmountIn;
542:         uint256 newReserveJ = _calcReserve(wellFunction(), reserves, j, newLpTokenSupply);
543:         tokenAmountOut = reserves[j] - newReserveJ;

recommended code :

```solidity
541:         tokenAmountOut = reserves[j] - (_calcReserve(wellFunction(), reserves, j, (totalSupply() - lpAmountIn)));

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L541-L543

[G-05] : Sort array offchain to check duplicates in O(n) instead of O(n^2)

Instead of using two for loops to check for duplicates, which runs in O(n^2) time and is expensive, the _tokens array can be sorted offchain (by converting all the elements of _tokens array which are of address type into uint160 type).This allows us to to check duplicates by simply ensuring that the current uint160 value is larger than the previous one in 0(n) time.

1 instance in 1 file

src/Well.sol :: init()

File: src/Well.sol
35:         IERC20[] memory _tokens = tokens();
36:         for (uint256 i; i < _tokens.length - 1; ++i) {
37:             for (uint256 j = i + 1; j < _tokens.length; ++j) {
38:                 _token = _tokens[i];
39:                 if (_token == _tokens[j]) {
40:                     revert DuplicateTokens(_token);
41:                 }
42:             }
43:         }

https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L35-L42

#0 - c4-pre-sort

2023-07-12T10:09:12Z

141345 marked the issue as high quality report

#1 - c4-sponsor

2023-07-17T18:57:08Z

publiuss marked the issue as sponsor confirmed

#2 - c4-judge

2023-08-05T10:59:30Z

alcueca marked the issue as grade-a

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