Platform: Code4rena
Start Date: 10/03/2022
Pot Size: $75,000 USDT
Total HM: 25
Participants: 54
Period: 7 days
Judge: pauliax
Total Solo HM: 10
Id: 97
League: ETH
Rank: 8/54
Findings: 2
Award: $2,135.11
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: sirhashalot
2075.2164 USDT - $2,075.22
The setPerTokenWalletCap()
function in WhitelistPeriodManager.sol contains a comment stating:
Special care must be taken when calling this function There are no checks for _perTokenWalletCap (since it's onlyOwner), but it's essential that it should be >= max lp provided by an lp. Checking this on chain will probably require implementing a bbst, which needs more bandwidth Call the view function getMaxCommunityLpPositon() separately before changing this value
Even if the manual step of calling the getMaxCommunityLpPositon()
function is properly performed, it is possible for a user to add liquidity to increase the maxLp
value in between when the getMaxCommunityLpPositon()
function is called and when the setPerTokenWalletCap()
function is called. Because this process is manual, this doesn't need to be bot frontrunning in the same block as when the setPerTokenWalletCap()
function is called, but can be cause by poor timing of an innocent unknowing user adding liquidity to the protocol. If this condition occurs, the liquidity provider will have provided more liquidity than the perTokenWalletCap limit, breaking the assumptions for this variable and leading to some denial of service conditions.
This edge situation can impact the setTotalCap()
function and the "perTokenTotalCap[_token]" state variable as well, but the "perTokenWalletCap[_token]" value would have to be reduced before the "perTokenTotalCap[_token]" value is reduced. The impact to setTotalCap()
follows the same execution path but adds the additional step of calling the setTotalCap()
function at the end of the process.
getMaxCommunityLpPositon(_token)
function to identify maxLp value to confirm new perTokenWalletCap value is below maxLp valuesetPerTokenWalletCap()
function to reduce "perTokenWalletCap[_token]" valueThis edge situation can impact the setTotalCap()
function and the "perTokenTotalCap[_token]" state variable as well, but the "perTokenWalletCap[_token]" value would have to be reduced before the "perTokenTotalCap[_token]" value is reduced. The impact to setTotalCap()
follows the same execution path but adds the additional step of calling the setTotalCap()
function at the end of the process.
Manual analysis
A programmatic solution is the only way to avoid these edge case scenarios, though it will increase gas consumption. To convert the manual calling of getMaxCommunityLpPositon(_token)
to a programmatic solution, add the following require statement next to the existing require statement of the setPerTokenWalletCap()
function:
require(_perTokenWalletCap <= getMaxCommunityLpPositon(_token), "ERR_PWC_GT_MCLP");
#0 - pauliax
2022-04-26T11:25:29Z
The concern is valid but I do not think that there is any profit for the attacker, and the impact for the regular users is minimal because this value can be updated anytime again by the owner, so I am hesitating if this should be of medium severity or lower, but because the warden provided a nice and comprehensive description, I will leave this in favor of warden.
🌟 Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xngndev, 0xwags, Cantor_Dust, CertoraInc, IllIllI, Jujic, Kenshin, Kiep, PPrieditis, TerrierLover, Tomio, WatchPug, antonttc, benk10, berndartmueller, bitbopper, csanuragjain, defsec, gzeon, hagrid, hickuphh3, kenta, minhquanym, oyc_109, pedroais, peritoflores, rfa, robee, saian, samruna, sirhashalot, throttle, wuwe1, z3s
59.8924 USDT - $59.89
The IERC20Upgradeable.sol is imported twice in LiquidityPool.sol, once from the SafeERC20Upgradeable.sol import and once from the direct import of IERC20Upgradeable.sol. The direct import line can be removed.
IERC20Upgradeable.sol is imported indirectly on line 9 and imported directly on line 10 of LiquidityPool.sol.
Manual analysis
Remove the following line from LiquidityPool.sol:
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
When an overflow or underflow is not possible, unchecked can be used to save the gas consumed by SafeMath in Solidity 0.8.0+
LiquidityPool.sol contains this code:
if (currentLiquidity < providedLiquidity) { uint256 liquidityDifference = providedLiquidity - currentLiquidity;
Because providedLiquidity - currentLiquidity
cannot underflow, unchecked can be used
Manual analysis
Replace the line of code copied above with the following
if (currentLiquidity < providedLiquidity) { unchecked { uint256 liquidityDifference = providedLiquidity - currentLiquidity; }
When an overflow or underflow is not possible, unchecked can be used to save the gas consumed by SafeMath in Solidity 0.8.0+
LiquidityPool.sol contains this code:
if (currentLiquidity < providedLiquidity) { uint256 liquidityDifference = providedLiquidity - currentLiquidity;
Because providedLiquidity - currentLiquidity
cannot underflow, unchecked can be used
Manual analysis
Replace the line of code copied above with the following
if (currentLiquidity < providedLiquidity) { unchecked { uint256 liquidityDifference = providedLiquidity - currentLiquidity; }
Strings are broken into 32 byte chunks for operations. Revert error strings over 32 bytes therefore consume extra gas as documented publicly
The locations where a default int value is set are listed below.
Manual analysis
Reducing revert error strings to under 32 bytes decreases deployment time gas and runtime gas when the revert condition is met. Alternatively, the code could be modified to use custom errors, introduced in Solidity 0.8.4: https://blog.soliditylang.org/2021/04/21/custom-errors/
Many functions can be declared external for gas savings
The functions that can benefit from this gas optimization and their corresponding contract are listed below.
ExecutorManager.sol
LiquidityFarming.sol
LiquidityPool.sol
LiquidityProviders.sol
WhitelistPeriodManager.sol
LPToken.sol
TokenManager.sol
Slither
Declare functions as external instead of public when possible for gas savings
uint256 variables are initialized to a default value of zero per Solidity docs: https://docs.soliditylang.org/en/latest/control-structures.html#default-value
Setting a variable to the default value is unnecessary. Shortening or removing lines of code where variables are initialized to zero can save gas. Here are a few articles describing this gas optimization: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6#53bd https://medium.com/coinmonks/gas-optimization-in-solidity-part-i-variables-9d5775e43dde#4135
The locations where a default int value is set are listed below.
Manual analysis
Instead of initializing a variable to zero, such as uint256 abc = 0;
, the line can be shortened to uint256 abc;
as Solidity automatically initializes uint variables to zero.
#0 - pauliax
2022-05-09T09:44:51Z
1 I think is not valid because the compiler automatically removes unused bytecode. 5 please read: https://ethereum.stackexchange.com/a/107939/17387