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: 59/74
Findings: 2
Award: $13.96
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: 0xprinc
Also found by: 0x11singh99, 0xAnah, 0xWaitress, 0xkazim, 2997ms, 33audits, 404Notfound, 8olidity, CRIMSON-RAT-REACH, CyberPunks, DanielWang888, Deekshith99, Eeyore, Eurovickk, Inspecktor, JGcarv, John, Jorgect, Kaysoft, LosPollosHermanos, MohammedRizwan, Qeew, QiuhaoLi, Rolezn, TheSavageTeddy, Topmark, Trust, Udsen, a3yip6, alexzoid, bigtone, codegpt, erebus, fatherOfBlocks, ginlee, glcanvas, hunter_w3b, josephdara, kaveyjoe, kutugu, mahdirostami, max10afternoon, oakcobalt, peanuts, pfapostol, ptsanev, qpzm, radev_sw, ravikiranweb3, sces60107, seth_lawson, te_aut, twcctop, zhaojie, ziyou-
6.0655 USDC - $6.07
Number | Issue Details | Instances |
---|---|---|
[L-01] | Check receiver address for address(0) before transferring or minting any tokens OR Ethers | 3 |
[L-02] | Check amount for 0 before transferring tokens | 6 |
[L-03] | Check receiver address for address(0) before transferring or minting any tokens OR Ethers | 1 |
Total 3 Low Level Issues
Number | Issue Details | Instances |
---|---|---|
[NC-01] | Named return is confusing, use explicit returns | 4 |
[NC-02] | Verify constructor params specially for immutables to check 0 values | 1 |
[NC-03] | Named params of mapping is more readable | 1 |
[NC-04] | Use ternary operator instead of if else | 3 |
[NC-05] | Some Contracts are not following proper solidity style guide layout | 1 |
Total 5 Non Critical Issues
3 Instances
recipient
is not address(0) before transfer ,here in _swapFrom to fail earlyFile: /src/Well.sol 195: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 212: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 304: toToken.safeTransfer(recipient, amountOut);
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L195 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L212 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L304
Check before trasferring zero money unnecessary. 6 Instances
amountIn
is not 0 before transferFile: /src/Well.sol 194: fromToken.safeTransferFrom(msg.sender, address(this), amountIn); 195: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 211: amountIn = _safeTransferFromFeeOnTransfer(fromToken, msg.sender, amountIn); 212: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 303: fromToken.safeTransferFrom(msg.sender, address(this), amountIn); 304: toToken.safeTransfer(recipient, amountOut);
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L194-L195 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L211-L212 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L303-L304
1 Instance
reserves[j]
before transferring tokens to outsideFile: /src/Well.sol 371: tokenOut.safeTransfer(recipient, amountOut); 372: reserves[j] -= amountOut;
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L371C5-L372C45
Return from function explicitly make code readable
4 Instances
File: src/pumps/MultiFlowPump.sol 171: function readLastReserves(address well) public view returns (uint256[] memory reserves) { ... 199: function _capReserve( bytes16 lastReserve, bytes16 reserve, bytes16 blocksPassed 203: ) internal view returns (bytes16 cappedReserve) { ... 222: function readLastInstantaneousReserves(address well) public view returns (uint256[] memory reserves) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L171 https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L199C3-L203C54 https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L222
File: /src/Aquifer.sol 86: function wellImplementation(address well) external view returns (address implementation) { 87: return wellImplementations[well];
https://github.com/code-423n4/2023-07-basin/blob/main/src/Aquifer.sol#L86C4-L87C42
Verify constructor params for 0 values by mistake otherwise they will be in the contract forever Or make contract re-deploying. 1 Instance
File: src/pumps/MultiFlowPump.sol 54: constructor(bytes16 _maxPercentIncrease, bytes16 _maxPercentDecrease, uint256 _blockTime, bytes16 _alpha) { LOG_MAX_INCREASE = ABDKMathQuad.ONE.add(_maxPercentIncrease).log_2(); // _maxPercentDecrease <= 100% if (_maxPercentDecrease > ABDKMathQuad.ONE) { revert InvalidMaxPercentDecreaseArgument(_maxPercentDecrease); } LOG_MAX_DECREASE = ABDKMathQuad.ONE.sub(_maxPercentDecrease).log_2(); BLOCK_TIME = _blockTime; // ALPHA <= 1 if (_alpha > ABDKMathQuad.ONE) { revert InvalidAArgument(_alpha); } ALPHA = _alpha; 68: }
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L54C3-L68C6
1 Instance
File: /src/Aquifer.sol 25: mapping(address => address) wellImplementations;
https://github.com/code-423n4/2023-07-basin/blob/main/src/Aquifer.sol#L25
Using ternary makes code concise and more gas efficient
3 Instance
File: /src/Aquifer.sol 40: if (immutableData.length > 0) { if (salt != bytes32(0)) { well = implementation.cloneDeterministic(immutableData, salt); } else { well = implementation.clone(immutableData); } } else { if (salt != bytes32(0)) { well = implementation.cloneDeterministic(salt); } else { well = implementation.clone(); } 52: }
https://github.com/code-423n4/2023-07-basin/blob/main/src/Aquifer.sol#L40C9-L52C10
In some contract variables are declared after function and state changing functions are after view functions which is not proper code layout of solidity. State variable declaration after functions is not proper code layout of solidity. 1 Instance
File: src/Well.sol 31: function init(string memory name, string memory symbol) public initializer { 32: __ERC20Permit_init(name); 33: __ERC20_init(name, symbol); ... 77: uint256 constant LOC_AQUIFER_ADDR = 0; uint256 constant LOC_TOKENS_COUNT = LOC_AQUIFER_ADDR + PACKED_ADDRESS; 79: uint256 constant LOC_WELL_FUNCTION_ADDR = LOC_TOKENS_COUNT + ONE_WORD;
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L31-L82
Recommendation: Update using below guide layout of variables Inside each contract, library or interface, use the following order:
Type declarations State variables Events Errors Modifiers Functions
https://docs.soliditylang.org/en/latest/style-guide.html#order-of-layout
Order of Functions๏ Ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier.
Functions should be grouped according to their visibility and ordered:
constructor receive function (if exists) fallback function (if exists) external public internal private
https://docs.soliditylang.org/en/latest/style-guide.html#order-of-functions
#0 - c4-pre-sort
2023-07-13T14:16:33Z
141345 marked the issue as high quality report
#1 - c4-sponsor
2023-08-03T20:30:58Z
publiuss marked the issue as sponsor confirmed
#2 - c4-judge
2023-08-05T10:42:52Z
alcueca marked the issue as grade-b
๐ 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
7.8853 USDC - $7.89
Number | Optimization Details | Instances |
---|---|---|
[G-01] | Do not calculate constant variables | 6 |
[G-02] | Check recipient for address(0) to fail early rather than checking inside function call | 4 |
[G-03] | Write for loops in more gas efficient way | 6 |
[G-04] | Memory variables should be cached in stack(if possible) variables rather than re-reading them from memory | 5 |
[G-05] | Function calls can be cached rather than re calling from same function with same value will save gas | 2 |
[G-06] | Use unchecked{} whenever underflow/overflow not possible saves 130 gas each time | 1 |
[G-07] | Using ternary operator instead of if else saves gas | 1 |
Total 7 issues
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas each time of use.
Total 6 instances - 1 files:
File : /src/Well.sol 29: bytes32 constant RESERVES_STORAGE_SLOT = bytes32(uint256(keccak256("reserves.storage.slot")) - 1); ... 78: uint256 constant LOC_TOKENS_COUNT = LOC_AQUIFER_ADDR + PACKED_ADDRESS; 79: uint256 constant LOC_WELL_FUNCTION_ADDR = LOC_TOKENS_COUNT + ONE_WORD; 80: uint256 constant LOC_WELL_FUNCTION_DATA_LENGTH = LOC_WELL_FUNCTION_ADDR + PACKED_ADDRESS; 81: uint256 constant LOC_PUMPS_COUNT = LOC_WELL_FUNCTION_DATA_LENGTH + ONE_WORD; 82: uint256 constant LOC_VARIABLE = LOC_PUMPS_COUNT + ONE_WORD;
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L29C5-L30C1 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L78C4-L82C64
Total 4 instances - 1 files:
recipient
can be checked for address(0)
in starting of external function to fail early.File : /src/Well.sol 195: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 212: amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); ... 398: lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, false); ... 407: lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, true);
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L195 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L212 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L398 https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L407
Loop can be written in more gas efficient way, by
6 instances - 1 file:
File: src/pumps/MultiFlowPump.sol 301: for (uint256 i; i < cumulativeReserves.length; ++i) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L301
Recommended Changes: Convert above for loop to this for making gas efficient saves more than 130 Gas per iteration
- for (uint256 i; i < cumulativeReserves.length; ++i) { ... } + uint256 _size = cumulativeReserves.length - 1; + for (uint256 i; i <= _size ; ) { ... + unchecked { + ++i; + } }
File: src/pumps/MultiFlowPump.sol 322: for (uint256 i; i < byteCumulativeReserves.length; ++i) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L322 Update like above
File: /src/Well.sol 36: for (uint256 i; i < _tokens.length - 1; ++i) { 37: for (uint256 j = i + 1; j < _tokens.length; ++j) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L36C6-L37C63 Update like first instance
File: /src/Well.sol 101: for (uint256 i; i < _pumps.length; i++) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L101C7-L101C50 Update like first instance
File: /src/Well.sol 363: for (uint256 i; i < _tokens.length; ++i) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L363C8-L363C51 Update like first instance
File: /src/Well.sol 382: for (uint256 i; i < _tokens.length; ++i) {
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L382C8-L382C51 Update like first instance
Note: These types of instances are many more in the Well.sol file so make sure to update them for gas efficiency.
It saves extra MLOAD opcode and saves gas
5 instances - 2 files:
pumpState.lastTimestamp
and reserves[i]
can be cachedFile: src/pumps/MultiFlowPump.sol 72: function update(uint256[] calldata reserves, bytes calldata) external { ... 83: if (pumpState.lastTimestamp == 0){ ... 109: uint256 deltaTimestamp = _getDeltaTimestamp(pumpState.lastTimestamp); ... 119: pumpState.lastReserves[i], (reserves[i] > 0 ? reserves[i] : 1).fromUIntToLog2(), blocksPassed ... 140: }
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L83 https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L109 https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L119
reserves[i]
can be cached to stack variableFile: src/pumps/MultiFlowPump.sol 153: if (reserves[i] == 0) return; 154: byteReserves[i] = reserves[i].fromUIntToLog2();
https://github.com/code-423n4/2023-07-basin/blob/main/src/pumps/MultiFlowPump.sol#L153C1-L154C60
tokenAmountsOut[i]
and minTokenAmountsOut[i]
can be cached to stack variableFile: src/Well.sol 473: for (uint256 i; i < _tokens.length; ++i) { if (tokenAmountsOut[i] < minTokenAmountsOut[i]) { revert SlippageOut(tokenAmountsOut[i], minTokenAmountsOut[i]); } _tokens[i].safeTransfer(recipient, tokenAmountsOut[i]); reserves[i] = reserves[i] - tokenAmountsOut[i]; 479: }
https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L473C6-L479C10
Note: These types of instances are many more in the Well.sol file so make sure to update them for gas efficiency.
2 instances - 1 file:
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: }
Because of above check, it can be decided that underflow/overflow not possible.
1 instance - 1 file:
i-1
can be unchecked due to line 93 for () condition initialization and incrementFile: src/libraries/LibLastReserveBytes.sol 93: for (uint256 i = 3; i <= n; ++i) { // `iByte` is the byte position for the current slot: // i 3 4 5 6 // iByte 1 1 2 2 97: iByte = ((i - 1) / 2) * 32; ... }
1 instance - 1 file:
File: /src/Aquifer.sol 40: if (immutableData.length > 0) { if (salt != bytes32(0)) { well = implementation.cloneDeterministic(immutableData, salt); } else { well = implementation.clone(immutableData); } } else { if (salt != bytes32(0)) { well = implementation.cloneDeterministic(salt); } else { 50: well = implementation.clone();
https://github.com/code-423n4/2023-07-basin/blob/main/src/Aquifer.sol#L40C9-L50C14
#0 - c4-pre-sort
2023-07-12T07:54:37Z
141345 marked the issue as low quality report
#1 - c4-judge
2023-08-05T11:27:14Z
alcueca marked the issue as grade-b