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: 68/198
Findings: 2
Award: $31.11
🌟 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.8655 USDC - $18.87
address(0x0)
when assigning values to address
state variables2022-09-vtvl/contracts/VTVLVesting.sol::83 => tokenAddress = _tokenAddress; 2022-09-vtvl/contracts/VTVLVesting.sol::106 => Claim storage _claim = claims[_recipient]; 2022-09-vtvl/contracts/VTVLVesting.sol::155 => _referenceTs = _claim.endTimestamp;
_safeMint()
should be used rather than _mint()
wherever possible_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function
2022-09-vtvl/contracts/token/FullPremintERC20Token.sol::12 => _mint(_msgSender(), supply_); 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::32 => mint(_msgSender(), initialSupply_); 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::45 => _mint(account, amount);
Avoid floating pragmas for non-library contracts.
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.
2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::2 => pragma solidity ^0.8.14;
The usage of deprecated library functions should be discouraged.
This issue is mostly related to OpenZeppelin libraries.
2022-09-vtvl/contracts/VTVLVesting.sol::12 => using SafeERC20 for IERC20;
2022-09-vtvl/contracts/AccessProtected.sol::30 => return _admins[_addressToCheck]; 2022-09-vtvl/contracts/VTVLVesting.sol::92 => return claims[_recipient]; 2022-09-vtvl/contracts/VTVLVesting.sol::187 => return (vestAmt > _claim.amountWithdrawn) ? vestAmt : _claim.amountWithdrawn; 2022-09-vtvl/contracts/VTVLVesting.sol::198 => return _baseVestedAmount(_claim, _referenceTs); 2022-09-vtvl/contracts/VTVLVesting.sol::208 => return _baseVestedAmount(_claim, _claim.endTimestamp); 2022-09-vtvl/contracts/VTVLVesting.sol::217 => return _baseVestedAmount(_claim, uint40(block.timestamp)) - _claim.amountWithdrawn; 2022-09-vtvl/contracts/VTVLVesting.sol::224 => return vestingRecipients; 2022-09-vtvl/contracts/VTVLVesting.sol::231 => return vestingRecipients.length;
indexed
fieldsEach event should use three indexed fields if there are three or more fields
2022-09-vtvl/contracts/AccessProtected.sol::14 => event AdminAccessSet(address indexed _admin, bool _enabled); 2022-09-vtvl/contracts/VTVLVesting.sol::59 => event ClaimCreated(address indexed _recipient, Claim _claim); 2022-09-vtvl/contracts/VTVLVesting.sol::64 => event Claimed(address indexed _recipient, uint112 _withdrawalAmount); 2022-09-vtvl/contracts/VTVLVesting.sol::69 => event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); 2022-09-vtvl/contracts/VTVLVesting.sol::74 => event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested);
2022-09-vtvl/contracts/VTVLVesting.sol::45 => bool isActive; // whether this claim is active (or revoked)
Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment
2022-09-vtvl/contracts/VTVLVesting.sol::266 => // Potential TODO: sanity check, if _linearVestAmount == 0, should we perhaps force that start and end ts are the same?
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents’ functions and change the visibility from external to public.
2022-09-vtvl/contracts/AccessProtected.sol::39 => function setAdmin(address admin, bool isEnabled) public onlyAdmin { 2022-09-vtvl/contracts/VTVLVesting.sol::196 => function vestedAmount(address _recipient, uint40 _referenceTs) public view returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::206 => function finalVestedAmount(address _recipient) public view returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::398 => function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::36 => function mint(address account, uint256 amount) public onlyAdmin {
2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::2 => pragma solidity ^0.8.14;
#0 - 0xean
2022-09-25T22:53:18Z
heavily docked for safeMint
🌟 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
immutable
2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::11 => uint256 public mintableSupply;
<array>.length
should not be looked up in every loop of a for
loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length. Storage array length checks incur an extra Gwarmaccess (100 gas) PER-LOOP.
2022-09-vtvl/contracts/VTVLVesting.sol::353 => 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
loops2022-09-vtvl/contracts/VTVLVesting.sol::353 => for (uint256 i = 0; i < length; i++) {
2022-09-vtvl/contracts/AccessProtected.sol::30 => return _admins[_addressToCheck]; 2022-09-vtvl/contracts/VTVLVesting.sol::198 => return _baseVestedAmount(_claim, _referenceTs); 2022-09-vtvl/contracts/VTVLVesting.sol::208 => return _baseVestedAmount(_claim, _claim.endTimestamp); 2022-09-vtvl/contracts/VTVLVesting.sol::217 => return _baseVestedAmount(_claim, uint40(block.timestamp)) - _claim.amountWithdrawn;
> 0
costs more gas than != 0
when used on a uint
in a require()
statement2022-09-vtvl/contracts/VTVLVesting.sol::107 => require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); 2022-09-vtvl/contracts/VTVLVesting.sol::256 => require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both 2022-09-vtvl/contracts/VTVLVesting.sol::257 => require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); 2022-09-vtvl/contracts/VTVLVesting.sol::263 => require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); 2022-09-vtvl/contracts/VTVLVesting.sol::449 => require(bal > 0, "INSUFFICIENT_BALANCE"); 2022-09-vtvl/contracts/token/FullPremintERC20Token.sol::11 => require(supply_ > 0, "NO_ZERO_MINT"); 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::27 => require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");
2022-09-vtvl/contracts/VTVLVesting.sol::27 => uint112 public numTokensReservedForVesting = 0; 2022-09-vtvl/contracts/VTVLVesting.sol::148 => uint112 vestAmt = 0; 2022-09-vtvl/contracts/VTVLVesting.sol::353 => for (uint256 i = 0; i < length; i++) {
++i
costs less gas than i++
, especially when it’s used in forloops (--i
/i--
too)2022-09-vtvl/contracts/VTVLVesting.sol::353 => for (uint256 i = 0; i < length; i++) {
require()
statements that use &&
Cost gasSee this issue for an example
2022-09-vtvl/contracts/VTVLVesting.sol::344 => require(_startTimestamps.length == length &&
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen 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.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
2022-09-vtvl/contracts/VTVLVesting.sol::27 => uint112 public numTokensReservedForVesting = 0; 2022-09-vtvl/contracts/VTVLVesting.sol::35 => uint40 startTimestamp; // When does the vesting start (40 bits is enough for TS) 2022-09-vtvl/contracts/VTVLVesting.sol::36 => uint40 endTimestamp; // When does the vesting end - the vesting goes linearly between the start and end timestamps 2022-09-vtvl/contracts/VTVLVesting.sol::37 => uint40 cliffReleaseTimestamp; // At which timestamp is the cliffAmount released. This must be <= startTimestamp 2022-09-vtvl/contracts/VTVLVesting.sol::38 => uint40 releaseIntervalSecs; // Every how many seconds does the vested amount increase. 2022-09-vtvl/contracts/VTVLVesting.sol::42 => uint112 linearVestAmount; // total entitlement 2022-09-vtvl/contracts/VTVLVesting.sol::43 => uint112 cliffAmount; // how much is released at the cliff 2022-09-vtvl/contracts/VTVLVesting.sol::44 => uint112 amountWithdrawn; // how much was withdrawn thus far - released at the cliffReleaseTimestamp 2022-09-vtvl/contracts/VTVLVesting.sol::64 => event Claimed(address indexed _recipient, uint112 _withdrawalAmount); 2022-09-vtvl/contracts/VTVLVesting.sol::69 => event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); 2022-09-vtvl/contracts/VTVLVesting.sol::74 => event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested); 2022-09-vtvl/contracts/VTVLVesting.sol::147 => function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::148 => uint112 vestAmt = 0; 2022-09-vtvl/contracts/VTVLVesting.sol::169 => uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs / _claim.releaseIntervalSecs) * _claim.releaseIntervalSecs; 2022-09-vtvl/contracts/VTVLVesting.sol::176 => uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; 2022-09-vtvl/contracts/VTVLVesting.sol::196 => function vestedAmount(address _recipient, uint40 _referenceTs) public view returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::206 => function finalVestedAmount(address _recipient) public view returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::215 => function claimableAmount(address _recipient) external view returns (uint112) { 2022-09-vtvl/contracts/VTVLVesting.sol::217 => return _baseVestedAmount(_claim, uint40(block.timestamp)) - _claim.amountWithdrawn; 2022-09-vtvl/contracts/VTVLVesting.sol::247 => uint40 _startTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::248 => uint40 _endTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::249 => uint40 _cliffReleaseTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::250 => uint40 _releaseIntervalSecs, 2022-09-vtvl/contracts/VTVLVesting.sol::251 => uint112 _linearVestAmount, 2022-09-vtvl/contracts/VTVLVesting.sol::252 => uint112 _cliffAmount 2022-09-vtvl/contracts/VTVLVesting.sol::292 => uint112 allocatedAmount = _cliffAmount + _linearVestAmount; 2022-09-vtvl/contracts/VTVLVesting.sol::319 => uint40 _startTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::320 => uint40 _endTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::321 => uint40 _cliffReleaseTimestamp, 2022-09-vtvl/contracts/VTVLVesting.sol::322 => uint40 _releaseIntervalSecs, 2022-09-vtvl/contracts/VTVLVesting.sol::323 => uint112 _linearVestAmount, 2022-09-vtvl/contracts/VTVLVesting.sol::324 => uint112 _cliffAmount 2022-09-vtvl/contracts/VTVLVesting.sol::335 => uint40[] memory _startTimestamps, 2022-09-vtvl/contracts/VTVLVesting.sol::336 => uint40[] memory _endTimestamps, 2022-09-vtvl/contracts/VTVLVesting.sol::337 => uint40[] memory _cliffReleaseTimestamps, 2022-09-vtvl/contracts/VTVLVesting.sol::338 => uint40[] memory _releaseIntervalsSecs, 2022-09-vtvl/contracts/VTVLVesting.sol::339 => uint112[] memory _linearVestAmounts, 2022-09-vtvl/contracts/VTVLVesting.sol::340 => uint112[] memory _cliffAmounts) 2022-09-vtvl/contracts/VTVLVesting.sol::371 => uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp)); 2022-09-vtvl/contracts/VTVLVesting.sol::377 => uint112 amountRemaining = allowance - usrClaim.amountWithdrawn; 2022-09-vtvl/contracts/VTVLVesting.sol::398 => function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { 2022-09-vtvl/contracts/VTVLVesting.sol::422 => uint112 finalVestAmt = finalVestedAmount(_recipient); 2022-09-vtvl/contracts/VTVLVesting.sol::429 => uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn; 2022-09-vtvl/contracts/VTVLVesting.sol::436 => emit ClaimRevoked(_recipient, amountRemaining, uint40(block.timestamp), _claim);
require()
or revert()
statements that check input arguments should be at the top of the function2022-09-vtvl/contracts/AccessProtected.sol::40 => require(admin != address(0), "INVALID_ADDRESS"); 2022-09-vtvl/contracts/VTVLVesting.sol::82 => require(address(_tokenAddress) != address(0), "INVALID_ADDRESS"); 2022-09-vtvl/contracts/VTVLVesting.sol::255 => require(_recipient != address(0), "INVALID_ADDRESS"); 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::37 => require(account != address(0), "INVALID_ADDRESS");
payable
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.
2022-09-vtvl/contracts/AccessProtected.sol::39 => function setAdmin(address admin, bool isEnabled) public onlyAdmin { 2022-09-vtvl/contracts/VTVLVesting.sol::398 => function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { 2022-09-vtvl/contracts/VTVLVesting.sol::418 => function revokeClaim(address _recipient) external onlyAdmin hasActiveClaim(_recipient) { 2022-09-vtvl/contracts/VTVLVesting.sol::446 => function withdrawOtherToken(IERC20 _otherTokenAddress) external onlyAdmin { 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::36 => function mint(address account, uint256 amount) public onlyAdmin {
2022-09-vtvl/contracts/AccessProtected.sol::2 => pragma solidity 0.8.14; 2022-09-vtvl/contracts/VTVLVesting.sol::2 => pragma solidity 0.8.14; 2022-09-vtvl/contracts/token/FullPremintERC20Token.sol::2 => pragma solidity 0.8.14; 2022-09-vtvl/contracts/token/VariableSupplyERC20Token.sol::2 => pragma solidity ^0.8.14;
calldata
instead of memory
for function parametersUse calldata instead of memory for function parameters. Having function arguments use calldata instead of memory can save gas.
There are several cases of function arguments using memory instead of calldata
2022-09-vtvl/contracts/VTVLVesting.sol::147 => function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) {
Checking non-zero transfer values can avoid an expensive external call and save gas.
While this is done at some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here
2022-09-vtvl/contracts/VTVLVesting.sol::388 => tokenAddress.safeTransfer(_msgSender(), amountRemaining); 2022-09-vtvl/contracts/VTVLVesting.sol::407 => tokenAddress.safeTransfer(_msgSender(), _amountRequested); 2022-09-vtvl/contracts/VTVLVesting.sol::450 => _otherTokenAddress.safeTransfer(_msgSender(), bal);
bools
for storage incurs overhead2022-09-vtvl/contracts/AccessProtected.sol::12 => mapping(address => bool) private _admins; // user address => admin? mapping 2022-09-vtvl/contracts/VTVLVesting.sol::45 => bool isActive; // whether this claim is active (or revoked)
if (<x> == true) => if (<x>), if (<x> == false) => if (!<x>)
2022-09-vtvl/contracts/VTVLVesting.sol::111 => require(_claim.isActive == true, "NO_ACTIVE_CLAIM");
public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
2022-09-vtvl/contracts/AccessProtected.sol::11 => abstract contract AccessProtected is Context {