Platform: Code4rena
Start Date: 20/09/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 198
Period: 3 days
Judge: 0xean
Total Solo HM: 2
Id: 164
League: ETH
Rank: 95/198
Findings: 2
Award: $27.97
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: AkshaySrivastav
Also found by: 0v3rf10w, 0x040, 0x1f8b, 0x4non, 0x5rings, 0x85102, 0xA5DF, 0xDecorativePineapple, 0xNazgul, 0xSky, 0xSmartContract, 0xbepresent, 0xf15ers, 0xmatt, 2997ms, Aeros, Aymen0909, B2, Bahurum, Bnke0x0, CertoraInc, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diana, Diraco, Dravee, ElKu, Funen, IllIllI, JC, JLevick, JohnSmith, JohnnyTime, KIntern_NA, Lambda, Margaret, MasterCookie, OptimismSec, RaymondFam, Respx, ReyAdmirado, RockingMiles, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, SooYa, StevenL, TomJ, Tomo, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, ak1, async, ayeslick, aysha, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, c3phas, carrotsmuggler, cccz, ch13fd357r0y3r, chatch, cryptostellar5, cryptphi, csanuragjain, d3e4, datapunk, delfin454000, dic0de, djxploit, durianSausage, eighty, erictee, exd0tpy, fatherOfBlocks, gogo, got_targ, hansfriese, ignacio, ikbkln, indijanc, innertia, joestakey, karanctf, ladboy233, leosathya, lukris02, martin, medikko, millersplanet, nalus, natzuu, neko_nyaa, neumo, obront, oyc_109, pcarranzav, peanuts, pedr02b2, pedroais, peiw, peritoflores, prasantgupta52, rajatbeladiya, rbserver, reassor, ret2basic, rokinot, romand, rotcivegaf, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, sorrynotsorry, supernova, tibthecat, tnevler, ubermensch, yongskiws, zzykxx, zzzitron
18.8574 USDC - $18.86
VariableSupplyERC20Token
contract does not have a locked pragma. Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
The comment on line 447 in line 34 does not take into consideration the overflow of the UNIX epoch time that will occur in 2038:
Gives us a range from 1 Jan 1970 (Unix epoch) up to approximately 35 thousand years from then (2^40 / (365 * 24 * 60 * 60) ~= 35k)
Not that this will affect the protocol soon but it's important to be aware of this information.
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x040, 0x1f8b, 0x4non, 0x85102, 0xA5DF, 0xDanielC, 0xNazgul, 0xSmartContract, 0xbepresent, 0xc0ffEE, 0xsam, 2997ms, AkshaySrivastav, Amithuddar, Atarpara, Aymen0909, B2, Bnke0x0, CertoraInc, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diana, DimitarDimitrov, Diraco, Funen, JC, JLevick, JohnSmith, Junnon, KIntern_NA, Lambda, MasterCookie, Matin, Noah3o6, Ocean_Sky, OptimismSec, RaymondFam, Respx, ReyAdmirado, RockingMiles, Rohan16, Rolezn, Ruhum, Saintcode_, Satyam_Sharma, Sm4rty, SnowMan, SooYa, Sta1400, StevenL, Tadashi, Tagir2003, TomJ, Tomio, Tomo, V_B, Waze, WilliamAmbrozic, Yiko, __141345__, a12jmx, adriro, ajtra, ak1, async, aysha, beardofginger, bobirichman, brgltd, bulej93, c3phas, carrotsmuggler, caventa, ch0bu, cryptostellar5, cryptphi, csanuragjain, d3e4, delfin454000, dharma09, djxploit, durianSausage, eighty, emrekocak, erictee, exd0tpy, fatherOfBlocks, francoHacker, gianganhnguyen, gogo, got_targ, hxzy, ignacio, ikbkln, imare, indijanc, jag, jpserrat, karanctf, ladboy233, leosathya, lucacez, lukris02, m9800, malinariy, martin, medikko, mics, millersplanet, mrpathfindr, nalus, natzuu, neko_nyaa, oyc_109, pauliax, peanuts, pedroais, peiw, pfapostol, prasantgupta52, rbserver, ret2basic, rokinot, rotcivegaf, rvierdiiev, sach1r0, samruna, seyni, slowmoses, subtle77, supernova, tgolding55, tibthecat, tnevler, w0Lfrum, yaemsobak, zishansami
9.1106 USDC - $9.11
FullPremintERC20Token
contract can be refactored by replacing the require
statement inside the constructor with an if
statement and a custom error:contract FullPremintERC20Tokem is ERC20 { constructor(string memory name_, string memory symbol_, uint256 supply_) ERC20(name_, symbol_) { `require`(supply_ > 0, "NO_ZERO_MINT"); _mint(_msgSender(), supply_); } } // GAS DEPLOYMENT COST => 1186340
As shown, the same contract saves almost 2,000 gas with this optimization:
contract FullPremintERC20Token is ERC20 { error NoZeroMint(); constructor(string memory name_, string memory symbol_, uint256 supply_) ERC20(name_, symbol_) { if(supply_== 0) revert NoZeroMint(); _mint(_msgSender(), supply_); } } // GAS DEPLOYMENT COST => 1184736
NOTE: The majority of the following optimizations refers to the exact same case of require-if statements.
onlyAdmin()
function modifier can be refactored. The require
statement can be replaced with anìf
statement and a custom error. Declare error:error AdminAccessRequired();
Replace line 25 with:
if(!_admins[_msgSender()) revert AdminAccessRequired();
setAdmin()
function can be refactored. The require
statement can be replaced with anìf
statement and a custom error. Declare error:error InvalidAddress();
Replace line 25 with:
if(admin == address(0)) revert InvalidAddress();
NOTE: As it will be demonstrated on following optimizations, the error InvalidAddress()
can be used on VariableSupplyERC20Token
and VariableSupplyERC20Token
and both of these contracts inherit from AccessProtected
contract. This makes the optimization greater because the error is declared just once and used in three contracts..
The require
statement on line 27 can be refactored by replacing the require
statement with an if
statement and a custom error. Declare error:
error InvalidAmount();
and replace line 27 with:
if (initialSupply_ == 0 || maxSupply_ == 0) revert InvalidAmount();
The require
statement on line 37 can be refactored by replacing the require
statement with an if
statement and a custom error. Use InvalidAddress();
error inherited from AccessProtected.sol
and replace line 37 with:
if (account == address(0)) revert InvalidAddress();
The require
statement on line 41 can be refactored by replacing the require
statement with an if
statement and a custom error. Use previoulsy declared error and replace line 41 with:
if (amount > mintableSupply) revert InvalidAmount();
The require
statements for this contract can be refactored. Declare the following errors:
error NoActiveClaim(); error ClaimAlreadyExists(); error InvalidVestedAmount(); error InvalidStartTimestamp(); error InvalidEndTimestamp(); error InvalidReleaseInterval(); error InvalidIntervalLength(); error InvalidCliff(); error InsufficientBalance(); error ArrayLengthMismatch(); error NothingToWithdraw(); error NoUnvestedAmount();
constructor
on line 81 has a require
statement that can be refactored like so:constructor(IERC20 _tokenAddress) { if(address(_tokenAddress) == address(0)) revert InvalidAddress(); tokenAddress = _tokenAddress; }
hasActiveClaim
modifier on line 105 has two require
statements that can be refactored like so:modifier hasActiveClaim(address _recipient) { Claim storage _claim = claims[_recipient]; if(_claim.startTimestamp == 0) revert NoActiveClaim(); if(!_claim.isActive) revert NoActiveClaim(); _; }
hasNoClaim
modifier on line 123 has a require
statement that can be refactored like so:modifier hasNoClaim(address _recipient) { Claim storage _claim = claims[_recipient]; if(_claim.startTimestamp > 0) revert ClaimAlreadyExists(); _; }
_createClaimUnchecked()
function on line 245 has eight require
statements can be refactored like so:function _createClaimUnchecked(...) private hasNoClaim(_recipient) { if(_recipient == address(0)) revert InvalidAddress(); if(_linearVestAmount+_cliffAmount == 0) revert InvalidVestedAmount(); if(_startTimestamp == 0) revert InvalidStartTimestamp(); if(_startTimestamp >= _endTimestamp) revert InvalidEndTimestamp(); if(_releaseIntervalSecs == 0) revert InvalidReleaseInterval(); if(!((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0)) revert InvalidIntervalLength(); if ( (_cliffReleaseTimestamp == 0 || _cliffAmount == 0 || _cliffReleaseTimestamp > _startTimestamp) || (_cliffReleaseTimestamp > 0 || _cliffAmount > 0) ) revert InvalidCliff(); Claim memory _claim = Claim({ ... }); uint112 allocatedAmount = _cliffAmount + _linearVestAmount; if(tokenAddress.balanceOf(address(this)) < numTokensReservedForVesting + allocatedAmount) revert InsufficientBalance(); claims[_recipient] = _claim; // store the claim numTokensReservedForVesting += allocatedAmount; // track the allocated amount vestingRecipients.push(_recipient); // add the vesting recipient to the list emit ClaimCreated(_recipient, _claim); // let everyone know }
createClaimsBatch()
function on line 333 has a require
statement that can be refactored:uint256 length = _recipients.length; if( startTimestamps.length != length || endTimestamps.length != length || cliffReleaseTimestamps.length != length || releaseIntervalsSecs.length != length || linearVestAmounts.length != length || cliffAmounts.length != length ) revert ArrayLengthMismatch();
withdraw()
function on line 364 has a require
statement that can be refactored:if (allowance <= usrClaim.amountWithdrawn) revert NothingToWithdraw();
withdrawAdmin()
function on line 398 has a require
statement that can be refactored:if(amountRemaining < _amountRequested) revert InsufficientBalance();
revokeClaim()
function on line 418 has a require
statement that can be refactored:if(_claim.amountWithdrawn >= finalVestAmt) revert NoUnvestedAmount();
withdrawOtherToken()
function on line 446 has a require
statement that can be refactored:if(_otherTokenAddress == tokenAddress) revert InvalidAddress(); uint256 bal = _otherTokenAddress.balanceOf(address(this)); if(bal ==0) revert InsufficientBalance();
NOTE: As you can see some custom errors can be re-used saving even more gas compared to the reason string
given inside require
statements, that are re-written every time.
for
loop inside the createClaimsBatch()
function uses post-increment to the variable i
, like so:for (uint256 i = 0; i < length; i++) {...}
i
in the loop.for (uint256 i = 0; i < length; ++i) {...}