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: 64/198
Findings: 3
Award: $43.66
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Czar102
Also found by: 0xDecorativePineapple, 0xNazgul, 0xSky, 0xbepresent, 0xmatt, Atarpara, Bahurum, DimitarDimitrov, Franfran, GimelSec, JGcarv, JLevick, Junnon, OptimismSec, Rolezn, Ruhum, Soosh, Tomo, Trust, __141345__, adriro, ajtra, bin2chen, cRat1st0s, cccz, cryptonue, d3e4, innertia, jag, joestakey, neumo, obront, pashov, pauliax, pcarranzav, peanuts, rajatbeladiya, rbserver, reassor, seyni, wagmi, zzykxx, zzzitron
0.7375 USDC - $0.74
The owner can mint as many tokens as wish when mintableSupply is 0 due to in that case the IF statemnt is going to be false and the next instruction is the _mint call.
Manual
Move the _mint call inside the if to ensure that both the if and require checks their conditions.
function mint(address account, uint256 amount) public onlyAdmin { require(account != address(0), "INVALID_ADDRESS"); // If we're using maxSupply, we need to make sure we respect it // mintableSupply = 0 means mint at will if(mintableSupply > 0) { require(amount <= mintableSupply, "INVALID_AMOUNT"); // We need to reduce the amount only if we're using the limit, if not just leave it be mintableSupply -= amount; + _mint(account, amount); } - _mint(account, amount); }
#0 - 0xean
2022-09-23T23:57:39Z
dupe of #3
🌟 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
30.6751 USDC - $30.68
[L01]. A floating pragma is set [L02]. Outdated compiler version
[NC01]. Event is missing indexed fields [NC02]. Public functions that are not being used inside de contract should be external [NC03]. Code in comments [NC04]. TODO comments means it's not a final version [NC05]. File is missing NatSpec
The contract VariableSupplyERC20Token have the pragma solidity directive ^0.8.14. It is recommended to specify a fixed compiler version to ensure that the bytecode produced does not vary between builds. This is especially important if you rely on bytecode-level verification of the code.
Lock the pragma.
VariableSupplyERC20Token.sol#L2
It's a best practice to use the latest compiler version. The specified minimum compiler version is quite old (0.8.14). Older compilers might be susceptible to some bugs. It's recommended changing the solidity version pragma to the latest version to enforce the use of an up-to-date compiler.
A list of known compiler bugs and their severity can be found here: https://etherscan.io/solcbuginfo
To check the bugfixed and improvements of latest versions see the following link
Update the pragma to 0.8.16
FullPremintERC20Token.sol#L2 VariableSupplyERC20Token.sol#L2 AccessProtected.sol#L2 VTVLVesting.sol#L2
Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
AccessProtected.sol#L14 VTVLVesting.sol#L59 VTVLVesting.sol#L64 VTVLVesting.sol#L69 VTVLVesting.sol#L74
AccessProtected.sol#L39 VTVLVesting.sol#L398
There are many places in the code with code commented. It's a good practice not to leave code in comments. Clean unnecesary comments.
VTVLVesting.sol#L114-L115 VTVLVesting.sol#L133 VTVLVesting.sol#L136-L138 VTVLVesting.sol#L261
There is a TODO comment inside the code what can mean that the code has not been finalished. Make sure that the code is finished before deployment in production.
FullPremintERC20Token.sol VariableSupplyERC20Token.sol AccessProtected.sol VTVLVesting.sol
🌟 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
12.236 USDC - $12.24
[G01]. Post-increment/decrement cost more gas then pre-increment/decrement [G02]. Split require statement instead of && [G03]. I = I + (-) X is cheaper in gas cost than I += (-=) X [G04]. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas [G05]. Don't compare boolean expressions to boolean literals [G06]. Using bools for storage incurs overhead [G07]. != 0 is cheaper than > 0 especially in state variables [G08]. Should use unchecked in arithmetic operations when it's not possible to overflow [G09]. Initialize variables with default values are not needed [G10]. Cache into local variable instead of access to the storage [G11]. Stack variable used as a cheaper cache for a state variable is only used once [G12]. Functions guaranteed to revert when called by normal users can be marked payable [G13]. Usage of uints/ints smaller than 32 Bytes (256 bits) incurs overhead
++I (--I) cost less gas than I++ (I--) especially in a loop.
contract TestPost { function testPost() public { uint256 i; i++; } } contract TestPre { function testPre() public { uint256 i; ++i; } }
Split of conditions of an require sentence in different requires sentences save gas.
contract TestA { function Test (uint256 paramA) public { require(paramA > 1 && paramA > 2); } } contract TestB { function Test (uint256 paramA) public { require(paramA > 1); require(paramA > 2); } }
VTVLVesting.sol#L344-L349 VTVLVesting.sol#L270
This is especially with state variables. In the following example I'm trying to demostrate the save gas in a loop of 10 iterations.
contract TestA { uint256 i; function Test () public { for(;i<10;){ i += 1; } } } contract TestB { uint256 i; function Test () public { for(;i<10;){ i = i + 1; } } }
VariableSupplyERC20Token.sol#L43 VTVLVesting.sol#L161 VTVLVesting.sol#L179 VTVLVesting.sol#L301 VTVLVesting.sol#L381 VTVLVesting.sol#L383 VTVLVesting.sol#L433
Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.
FullPremintERC20Token.sol#L11 VariableSupplyERC20Token.sol#L27 VariableSupplyERC20Token.sol#L37 VariableSupplyERC20Token.sol#L41 AccessProtected.sol#L25 AccessProtected.sol#L40 VTVLVesting.sol#L82 VTVLVesting.sol#L107 VTVLVesting.sol#L111 VTVLVesting.sol#L129 VTVLVesting.sol#L255 VTVLVesting.sol#L256 VTVLVesting.sol#L257 VTVLVesting.sol#L262 VTVLVesting.sol#L263 VTVLVesting.sol#L264 VTVLVesting.sol#L270 VTVLVesting.sol#L295 VTVLVesting.sol#L344 VTVLVesting.sol#L374 VTVLVesting.sol#L402 VTVLVesting.sol#L426 VTVLVesting.sol#L447 VTVLVesting.sol#L449
if (<x> == true) => if (<x>), if (<x> == false) => if (!<x>)
contract TestA { function Test (bool paramA) public { require(paramA == true, "Test bool"); } } contract TestB { function Test (bool paramA) public { require(paramA, "Test bool"); } }
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past.
AccessProtected.sol#L12 VTVLVesting.sol#L45
In cases where we used a variable from state assigned to a local variable it's the same gas save.
contract TestA { uint256 _paramA; function Test () public returns (bool) { return _paramA > 0; } } contract TestB { uint256 _paramA; function Test () public returns (bool) { return _paramA != 0; } } contract TestC { uint256 _paramA; function Test () public returns (bool) { uint256 aux = _paramA; return aux > 0; } } contract TestD { uint256 _paramA; function Test () public returns (bool) { uint256 aux = _paramA; return aux != 0; } }
FullPremintERC20Token.sol#L11 VariableSupplyERC20Token.sol#L27 VariableSupplyERC20Token.sol#L31 VariableSupplyERC20Token.sol#L40 VTVLVesting.sol#L107 VTVLVesting.sol#L256 VTVLVesting.sol#L257 VTVLVesting.sol#L263 VTVLVesting.sol#L272 VTVLVesting.sol#L273 VTVLVesting.sol#L449
This situation ocurrs especially in loops
Example,
- for (uint256 i = 0; i < length; i++) { + for (uint256 i = 0; i < length;) { _createClaimUnchecked(_recipients[i], _startTimestamps[i], _endTimestamps[i], _cliffReleaseTimestamps[i], _releaseIntervalsSecs[i], _linearVestAmounts[i], _cliffAmounts[i]); + unchecked { + i++; + } }
VTVLVesting.sol#L292 VTVLVesting.sol#L301 VTVLVesting.sol#L353 VTVLVesting.sol#L377 VTVLVesting.sol#L429 VariableSupplyERC20Token.sol#L43
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address,...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
VTVLVesting.sol#L27 VTVLVesting.sol#L148 VTVLVesting.sol#L353
When it's going to access to a storage variable more than once it's a best practice to use a local variable to save gas.
In this case the function it's accessing to the storage variable mintableSupply twice. It's possible to store the value in a local variable and then use it.
function mint(address account, uint256 amount) public onlyAdmin { require(account != address(0), "INVALID_ADDRESS"); // If we're using maxSupply, we need to make sure we respect it // mintableSupply = 0 means mint at will + uint256 _mintableSupply = mintableSupply; - if(mintableSupply > 0) { - require(amount <= mintableSupply, "INVALID_AMOUNT"); + if(_mintableSupply > 0) { + require(amount <= _mintableSupply, "INVALID_AMOUNT"); // We need to reduce the amount only if we're using the limit, if not just leave it be mintableSupply -= amount; } _mint(account, amount); }
VariableSupplyERC20Token.sol#L36-L46
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend
VTVLVesting.sol#L124 VTVLVesting.sol#L197 VTVLVesting.sol#L400
If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
VariableSupplyERC20Token.sol#L36
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. Use a larger size then downcast where needed
VTVLVesting.sol#L27 VTVLVesting.sol#L35 VTVLVesting.sol#L36 VTVLVesting.sol#L37 VTVLVesting.sol#L38 VTVLVesting.sol#L42 VTVLVesting.sol#L43 VTVLVesting.sol#L44 VTVLVesting.sol#L148 VTVLVesting.sol#L167 VTVLVesting.sol#L169 VTVLVesting.sol#L170 VTVLVesting.sol#L176 VTVLVesting.sol#L292 VTVLVesting.sol#L371 VTVLVesting.sol#L377 VTVLVesting.sol#L422 VTVLVesting.sol#L429