Platform: Code4rena
Start Date: 26/09/2022
Pot Size: $50,000 USDC
Total HM: 13
Participants: 113
Period: 5 days
Judge: 0xean
Total Solo HM: 6
Id: 166
League: ETH
Rank: 27/113
Findings: 3
Award: $111.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xSmartContract
Also found by: 0xDecorativePineapple, 0xNazgul, 0xmatt, Jeiwan, Trust, berndartmueller, brgltd, catchup, ch13fd357r0y3r, cryptonue, ladboy233, minhtrng, neko_nyaa, rbserver, rvierdiiev, s3cunda
35.4829 USDC - $35.48
When a pool is deployed, the pool's initial globalState.price value is unset (and therefore 0). The initial price value is set via a call to AlgebraPool.sol#initialize. As the function is accessible by anyone, it could be front-run to set an arbitrary price that could be used to drain initial deposits.
Assuming the real value of USDC/DAI is at a point in time 1 USDC == 1 DAI:
This finding is inherited from UniSwap V3's pool initialization pattern.
VSCode, Remix
Because of the structure of AlgebraSwap, passing parameters at the factory level to set price during the deployment process may be suboptimal as real-world prices could change by the time initial deposits are made. Implementing a modifier restricting access to the pool creator would stop rogue initialize() calls. The modifier may also benefit from including the factory owner's address, in case of pools being created but never initialized (e.g. through griefing with popular tokens where pools don't yet exist).
DataStorageOperator.sol's initialize() function has an onlyPool modifier to stop others from front-running data storage initialization.
There are two obvious approaches to this, each with pros and cons.
The originating EOA could be identified in the pool constructor via tx.origin. The disadvantage is that if a pool is created via a contract, tx.origin may not be available or accurate under all circumstances. Using tx.origin also comes with it's own issues.
The other approach is to pass msg.sender from the factory via the deployer call as an argument, which in turn would pass this on to the pool's constructor. This is preferable as the pool creation source is properly preserved.
These approaches retain the contract's current structure, which may be preferable to moving price initialization purely to the constructor.
#0 - 0xean
2022-10-02T21:43:35Z
dupe of #84
🌟 Selected for report: 0xNazgul
Also found by: 0x1f8b, 0x52, 0xDecorativePineapple, 0xSmartContract, 0xmatt, Aeros, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, DimitarDimitrov, IllIllI, JC, Jeiwan, Lambda, Matin, Migue, Mukund, Ocean_Sky, Olivierdem, RaymondFam, RockingMiles, Rolezn, Ruhum, Satyam_Sharma, Shinchan, Tomo, Trabajo_de_mates, V_B, Waze, __141345__, a12jmx, ajtra, asutorufos, aysha, brgltd, bulej93, carrotsmuggler, catchup, cccz, chrisdior4, cryptonue, cryptphi, d3e4, defsec, delfin454000, durianSausage, erictee, fatherOfBlocks, gogo, kaden, karanctf, ladboy233, lukris02, mahdikarimi, martin, mics, natzuu, oyc_109, p_crypt0, pedr02b2, rbserver, reassor, rotcivegaf, rvierdiiev, sikorico, slowmoses, sorrynotsorry, tnevler, trustindistrust
52.0364 USDC - $52.04
Zero-address checks are a common best practice for input validation of critical address parameters, particularly in constructors and setters. Accidental use of zero-addresses may result in unexpected exceptions, failures or require the redeployment of contracts.
The project already uses zero-address checks in some places.
If it is necessary to be able to set an address to 0 consider implementing a two-step or renounce function if the address is to be subject to access control.
The following instances were identified where zero-address checks should be added.
In AlgebraFactory.sol:
In AlgebraPool.sol:
Several address parameters in AlgebraFactory are changeable in a single step. Although these functions are locked to the owner, in the event that the owner sets an incorrect or null value, some of these settings could have a significant impact on the contract's function, such as permanently losing ownership. This could require a redeployment to address, which could be disruptive.
Consider a two-step nominate and claim process, where the change is nominated by the owner, but must be claimed by the process recipient to succeed. This ensures the updated address is valid.
Instances found:
In other files:
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x5rings, 0xNazgul, 0xRoxas, 0xSmartContract, 0xbepresent, 0xmatt, Aeros, Amithuddar, Awesome, Aymen0909, B2, Bnke0x0, ChristianKuri, CodingNameKiki, Deivitto, Diraco, Fitraldys, HardlyCodeMan, JC, Mukund, Noah3o6, Olivierdem, RaymondFam, ReyAdmirado, RockingMiles, Rolezn, Ruhum, Saintcode_, Shinchan, SnowMan, TomJ, Tomio, Tomo, V_B, Waze, __141345__, ajtra, asutorufos, aysha, beardofginger, bobirichman, brgltd, bulej93, c3phas, ch0bu, cryptonue, defsec, delfin454000, dharma09, durianSausage, emrekocak, erictee, fatherOfBlocks, francoHacker, gianganhnguyen, gogo, imare, kaden, karanctf, ladboy233, lukris02, m_Rassska, martin, medikko, mics, natzuu, oyc_109, peiw, rbserver, ret2basic, rotcivegaf, saian, shark, slowmoses, tnevler, trustindistrust, zeesaw, zishansami
24.0179 USDC - $24.02
When dealing with unsigned integer types, comparisons with != 0
are cheaper than with > 0
. This should only be done with unsigned variables, immutables and constants, unless the author knows that the value will always be 0 or positive.
src/core/contracts/AlgebraPool.sol::224 => require(currentLiquidity > 0, 'NP'); // Do not recalculate the empty ranges src/core/contracts/AlgebraPool.sol::228 => if (_liquidityCooldown > 0) { src/core/contracts/AlgebraPool.sol::434 => require(liquidityDesired > 0, 'IL'); src/core/contracts/AlgebraPool.sol::469 => require(liquidityActual > 0, 'IIL2'); src/core/contracts/AlgebraPool.sol::617 => if (communityFee > 0) { src/core/contracts/AlgebraPool.sol::667 => if (communityFee > 0) { src/core/contracts/AlgebraPool.sol::808 => if (cache.communityFee > 0) { src/core/contracts/AlgebraPool.sol::814 => if (currentLiquidity > 0) cache.totalFeeGrowth += FullMath.mulDiv(step.feeAmount, Constants.Q128, currentLiquidity); src/core/contracts/AlgebraPool.sol::898 => require(_liquidity > 0, 'L'); src/core/contracts/AlgebraPool.sol::924 => if (paid0 > 0) { src/core/contracts/AlgebraPool.sol::927 => if (_communityFeeToken0 > 0) { src/core/contracts/AlgebraPool.sol::938 => if (paid1 > 0) { src/core/contracts/AlgebraPool.sol::941 => if (_communityFeeToken1 > 0) { src/core/contracts/DataStorageOperator.sol::46 => require(_feeConfig.gamma1 != 0 && _feeConfig.gamma2 != 0 && _feeConfig.volumeGamma != 0, 'Gammas must be > 0'); src/core/contracts/DataStorageOperator.sol::138 => if (volume >= 2**192) volumeShifted = (type(uint256).max) / (liquidity > 0 ? liquidity : 1); src/core/contracts/DataStorageOperator.sol::139 => else volumeShifted = (volume << 64) / (liquidity > 0 ? liquidity : 1); src/core/contracts/libraries/DataStorage.sol::80 => last.secondsPerLiquidityCumulative += ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)); // just timedelta if liquidity == 0 src/core/contracts/libraries/PriceMovementMath.sol::52 => require(price > 0); src/core/contracts/libraries/PriceMovementMath.sol::53 => require(liquidity > 0);
Enumerating storage array length inside a loop creates an extra SLOAD operation for each iteration after the first. It is more gas efficient to use a temporary in-function variable to store the length, then read that value in the loop.
src/core/contracts/libraries/DataStorage.sol::307 => for (uint256 i = 0; i < secondsAgos.length; i++) {
Gas costs vary for different types of increment. In loops, the most gas-optimal option is unchecked {++i};
, followed by ++i
. In DataStorage.sol the following loop is defined:
for (uint256 i = 0; i < secondsAgos.length; i++) {
Aside from storing array length outside of the loop (G02), this loop can be gas optimized as follows:
for (uint256 i = 0; i < secondsAgos.length; ++i) { // More readable, predecrement
If the authors are confident that secondsAgos.length will never exceed uint256 size using unchecked will provide further gas savings:
for (uint256 i = 0; i < secondsAgos.length;) { // ... rest of loop code to end of L314 unchecked { ++i; } }