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
Rank: 31/74
Findings: 1
Award: $58.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SM3_SS
Also found by: 0x11singh99, 0xAnah, 0xSmartContract, 0xn006e7, 0xprinc, DavidGiladi, ElCid, JCN, K42, MIQUINHO, Raihan, Rolezn, SAAJ, SY_S, Strausses, TheSavageTeddy, bigtone, erebus, hunter_w3b, josephdara, lsaudit, mahdirostami, oakcobalt, peanuts, pfapostol, seth_lawson
58.4732 USDC - $58.47
S.N. | Issues | Instances | Gas Savings |
---|---|---|---|
G-01 | Fail as early as possible | 4 | - |
G-02 | Use do while loops instead of for loops | 27 | 896 + additional gas |
G-03 | Cache calldata/memory pointers for complex types to avoid complex offset calculations | 24 | 310 + additional gas |
G-04 | Avoid caching the result of a function call when that result is being used only once | 7 | 72 |
G-05 | Sort array offchain to check duplicates in O(n) instead of O(n^2) | 1 | - |
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
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
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
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
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
do while
loops instead of for
loopsA 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.
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | update | 3157 | 49921 | 51066 | 83523 | 22 |
After | update | 3132 | 49874 | 51011 | 83468 | 22 |
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
update
function as it is an internal function used by update
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | update | 3157 | 49921 | 51066 | 83523 | 22 |
After | update | 3157 | 49906 | 51066 | 83523 | 22 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readLastReserves | 11426 | 11454 | 11454 | 11482 | 4 |
After | readLastReserves | 11365 | 11393 | 11393 | 11421 | 4 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readLastInstantaneousReserves | 12063 | 12682 | 12196 | 14273 | 4 |
After | readLastInstantaneousReserves | 12002 | 12621 | 12135 | 14212 | 4 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readInstantaneousReserves | 57570 | 60298 | 57773 | 65551 | 6 |
After | readInstantaneousReserves | 57509 | 60237 | 57712 | 65490 | 6 |
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
readCumulativeReserves
function as it is an internal function used by readCumulativeReserves
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readCumulativeReserves | 44868 | 51151 | 48231 | 57381 | 14 |
After | readCumulativeReserves | 44810 | 51093 | 48173 | 57323 | 14 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readTwaReserves | 45336 | 58079 | 60889 | 65255 | 8 |
After | readTwaReserves | 45336 | 58036 | 60831 | 65197 | 8 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | shift | 14099 | 29365 | 30336 | 42691 | 4 |
After | shift | 14039 | 29307 | 30278 | 42633 | 4 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getShiftOut | 11150 | 11289 | 11359 | 11359 | 3 |
After | getShiftOut | 11092 | 11231 | 11301 | 11301 | 3 |
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
addLiquidity
function as it is an internal function used by addLiquidity
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | addLiquidity | 131592 | 131592 | 131592 | 131592 | 4 |
After | addLiquidity | 131548 | 131548 | 131548 | 131548 | 4 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | removeLiquidity | 5797 | 58448 | 76460 | 89360 | 5 |
After | removeLiquidity | 5797 | 58408 | 76402 | 89302 | 5 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | removeLiquidityImbalanced | 5774 | 72642 | 89489 | 97989 | 5 |
After | removeLiquidityImbalanced | 5774 | 72595 | 89431 | 97931 | 5 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getRemoveLiquidityImbalancedIn | 6836 | 10502 | 11336 | 13336 | 3 |
After | getRemoveLiquidityImbalancedIn | 6777 | 10443 | 11277 | 13277 | 3 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | sync | 16007 | 16007 | 16007 | 16007 | 3 |
After | sync | 15949 | 15949 | 15949 | 15949 | 3 |
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | skim | 53028 | 53028 | 53028 | 53028 | 1 |
After | skim | 52970 | 52970 | 52970 | 52970 | 1 |
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
sync
function as it is an internal function used by sync
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | sync | 16007 | 16007 | 16007 | 16007 | 3 |
After | sync | 15949 | 15949 | 15949 | 15949 | 3 |
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
swapTo
function as it is an internal function used by swapTo
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | swapTo | 5771 | 33401 | 27108 | 69480 | 6 |
After | swapTo | 5771 | 33355 | 27056 | 69422 | 6 |
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
shift
function as it is an internal function used by shift
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | shift | 14099 | 29365 | 30336 | 42691 | 4 |
After | shift | 14071 | 29341 | 30316 | 42663 | 4 |
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
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
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
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
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
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
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
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
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
reserves[i]
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | update | 3157 | 49921 | 51066 | 83523 | 22 |
After | update | 3157 | 49889 | 50996 | 83453 | 22 |
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
reserves[i]
, this function gas saving has been calculated by using update
function as it is an internal function used by update
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | update | 3157 | 49921 | 51066 | 83523 | 22 |
After | update | 3157 | 49901 | 51066 | 83523 | 22 |
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
_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
tokenAmountsIn[i]
, this function gas saving has been calculated by using addLiquidity
function as it is an internal function used by addLiquidity
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | addLiquidity | 131592 | 131592 | 131592 | 131592 | 4 |
After | addLiquidity | 131452 | 131452 | 131452 | 131452 | 4 |
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
tokenAmountsOut[i]
and minTokenAmountsOut[i]
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | removeLiquidity | 5797 | 50448 | 36660 | 89360 | 5 |
After | removeLiquidity | 5797 | 50386 | 36518 | 89218 | 5 |
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
tokenAmountsOut[i]
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | removeLiquidityImbalanced | 5774 | 72642 | 89489 | 97989 | 5 |
After | removeLiquidityImbalanced | 5774 | 72586 | 89419 | 97919 | 5 |
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
_pumps[i]
and also _pumps[i].data
in memoryFile: 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
_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
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.
Here, slot
cached in line 171
has been used only once in line 172
.
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | readLastReserves | 11426 | 11454 | 11454 | 11482 | 4 |
After | readLastReserves | 11421 | 11449 | 11449 | 11477 | 4 |
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
Here, dataLoc
cached in line 91
has been used only once in line 92
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | wellFunction | 2049 | 2049 | 2049 | 2049 | 9 |
After | wellFunction | 2036 | 2036 | 2036 | 2036 | 9 |
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
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | removeLiquidity | 5797 | 58448 | 76460 | 89360 | 5 |
After | removeLiquidity | 5797 | 58433 | 76441 | 89341 | 5 |
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
Here, lpTokenSupply
cached in line 488
has been used only once in line 490
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getRemoveLiquidityOut | 7148 | 10398 | 10398 | 13648 | 2 |
After | getRemoveLiquidityOut | 7135 | 10385 | 10385 | 13635 | 2 |
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
Here, j
cached in line 525
has been used only once in line 526
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getRemoveLiquidityOneTokenOut | 13321 | 13321 | 13321 | 13321 | 2 |
After | getRemoveLiquidityOneTokenOut | 13308 | 13308 | 13308 | 13308 | 2 |
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
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
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getRemoveLiquidityOneTokenOut | 13321 | 13321 | 13321 | 13321 | 2 |
After | getRemoveLiquidityOneTokenOut | 13308 | 13308 | 13308 | 13308 | 2 |
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
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
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