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: 96/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
While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
It is recommended to pin to a concrete compiler version.
contracts/token/VariableSupplyERC20Token.sol:L2 pragma solidity ^0.8.14;
Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment.
contracts/VTVLVesting.sol:L266 // Potential TODO: sanity check, if _linearVestAmount == 0, should we perhaps force that start and end ts are the same?
π 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
// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 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
contracts/AccessProtected.sol:L12 mapping(address => bool) private _admins; // user address => admin? mapping
revert()
/require()
string to save gasCustom errors are available from solidity version 0.8.4, it saves around 50 gas each time they are hit by avoiding having to allocate and store the revert string.
contracts/VTVLVesting.sol:L82 require(address(_tokenAddress) != address(0), "INVALID_ADDRESS"); contracts/VTVLVesting.sol:L107 require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); contracts/VTVLVesting.sol:L111 require(_claim.isActive == true, "NO_ACTIVE_CLAIM"); contracts/VTVLVesting.sol:L129 require(_claim.startTimestamp == 0, "CLAIM_ALREADY_EXISTS"); contracts/VTVLVesting.sol:L255 require(_recipient != address(0), "INVALID_ADDRESS"); contracts/VTVLVesting.sol:L256 require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both contracts/VTVLVesting.sol:L257 require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); contracts/VTVLVesting.sol:L262 require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp contracts/VTVLVesting.sol:L263 require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); contracts/VTVLVesting.sol:L264 require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); contracts/VTVLVesting.sol:L295 require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); contracts/VTVLVesting.sol:L374 require(allowance > usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW"); contracts/VTVLVesting.sol:L402 require(amountRemaining >= _amountRequested, "INSUFFICIENT_BALANCE"); contracts/VTVLVesting.sol:L426 require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); contracts/VTVLVesting.sol:L447 require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); // tokenAddress address is already sure to be nonzero due to constructor contracts/VTVLVesting.sol:L449 require(bal > 0, "INSUFFICIENT_BALANCE"); contracts/AccessProtected.sol:L25 require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED"); contracts/AccessProtected.sol:L40 require(admin != address(0), "INVALID_ADDRESS"); contracts/token/VariableSupplyERC20Token.sol:L27 require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT"); contracts/token/VariableSupplyERC20Token.sol:L37 require(account != address(0), "INVALID_ADDRESS"); contracts/token/VariableSupplyERC20Token.sol:L41 require(amount <= mintableSupply, "INVALID_AMOUNT"); contracts/token/FullPremintERC20Token.sol:L11 require(supply_ > 0, "NO_ZERO_MINT");
public functions that are never called by the contract should be declared external to save gas.
contracts/VTVLVesting.sol:L398 function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { contracts/AccessProtected.sol:L39 function setAdmin(address admin, bool isEnabled) public onlyAdmin {
> 0
costs more gas than != 0
when used on a uint in a require()
statement.When working with unsigned integer types, comparisons with != 0 are cheaper than with > 0 . This changes saves 6 gas per instance.
contracts/VTVLVesting.sol:L107 require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); contracts/VTVLVesting.sol:L256 require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both contracts/VTVLVesting.sol:L257 require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); contracts/VTVLVesting.sol:L263 require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); contracts/VTVLVesting.sol:L449 require(bal > 0, "INSUFFICIENT_BALANCE"); contracts/token/VariableSupplyERC20Token.sol:L27 require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT"); contracts/token/FullPremintERC20Token.sol:L11 require(supply_ > 0, "NO_ZERO_MINT");
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.
contracts/VTVLVesting.sol:L398 function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { contracts/VTVLVesting.sol:L418 function revokeClaim(address _recipient) external onlyAdmin hasActiveClaim(_recipient) { contracts/VTVLVesting.sol:L446 function withdrawOtherToken(IERC20 _otherTokenAddress) external onlyAdmin { contracts/AccessProtected.sol:L39 function setAdmin(address admin, bool isEnabled) public onlyAdmin { contracts/token/VariableSupplyERC20Token.sol:L36 function mint(address account, uint256 amount) public onlyAdmin {
X += Y
costs more gas than X = X + Y
for state variables.Consider changing X += Y
to X = X + Y
to save gas.
contracts/VTVLVesting.sol:L161 vestAmt += _claim.cliffAmount; contracts/VTVLVesting.sol:L179 vestAmt += linearVestAmount; contracts/VTVLVesting.sol:L301 numTokensReservedForVesting += allocatedAmount; // track the allocated amount contracts/VTVLVesting.sol:L381 usrClaim.amountWithdrawn += amountRemaining;
Saves 6 gas per loop.
contracts/VTVLVesting.sol:L353 for (uint256 i = 0; i < length; i++) {
require()
statements that use && saves gas.Consider splitting the require()
statements to save gas.
contracts/VTVLVesting.sol:L270 require( contracts/VTVLVesting.sol:L344 require(_startTimestamps.length == length &&
Explicit initialization with zero is not required for variable declaration because uints are 0 by default. Removing this will reduce contract size and save a bit of gas.
contracts/VTVLVesting.sol:L27 uint112 public numTokensReservedForVesting = 0; contracts/VTVLVesting.sol:L148 uint112 vestAmt = 0; contracts/VTVLVesting.sol:L353 for (uint256 i = 0; i < length; i++) {
++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for- and while-loops.The unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop.
contracts/VTVLVesting.sol:L353 for (uint256 i = 0; i < length; i++) {