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: 73/198
Findings: 2
Award: $30.68
🌟 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
21.361 USDC - $21.36
The critical procedures should be two step process.
There is 1 instance of this issue:
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol#L39-L43
File: contracts/AccessProtected.sol function setAdmin(address admin, bool isEnabled) public onlyAdmin { require(admin != address(0), "INVALID_ADDRESS"); _admins[admin] = isEnabled; emit AdminAccessSet(admin, isEnabled); }
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
In the contracts, floating pragmas should not be used. 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.
There is 1 instance of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol#L2
File: contracts/token/VariableSupplyERC20Token.sol Line: 2 pragma solidity ^0.8.14;
Lock the pragma version
There are 3 instances of this issue
File: contracts/token/VariableSupplyERC20Token.sol #1 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); }
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L317-L327
File: contracts/VTVLVesting.sol #2 function createClaim( address _recipient, uint40 _startTimestamp, uint40 _endTimestamp, uint40 _cliffReleaseTimestamp, uint40 _releaseIntervalSecs, uint112 _linearVestAmount, uint112 _cliffAmount ) external onlyAdmin { _createClaimUnchecked(_recipient, _startTimestamp, _endTimestamp, _cliffReleaseTimestamp, _releaseIntervalSecs, _linearVestAmount, _cliffAmount); }
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L446-L451
File: contracts/VTVLVesting.sol #3 function withdrawOtherToken(IERC20 _otherTokenAddress) external onlyAdmin { require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); // tokenAddress address is already sure to be nonzero due to constructor uint256 bal = _otherTokenAddress.balanceOf(address(this)); require(bal > 0, "INSUFFICIENT_BALANCE"); _otherTokenAddress.safeTransfer(_msgSender(), bal); }
When deploying contracts, you should use the latest released version of Solidity. Apart from exceptional cases, only the latest version receives security fixes. Furthermore, breaking changes as well as new features are introduced regularly.
There are 4 instances of this issue.
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/FullPremintERC20Token.sol#L2
File: contracts/token/FullPremintERC20Token.sol #1 pragma solidity 0.8.14;
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol#L2
File: contracts/token/VariableSupplyERC20Token.sol #2 pragma solidity ^0.8.14;
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol#L2
File: contracts/AccessProtected.sol #3 pragma solidity 0.8.14;
File: contracts/VTVLVesting.sol #4 pragma solidity 0.8.14;
Update to the latest released version of Solidity
There are 5 instances of this issue
File: contracts/token/FullPremintERC20Token.sol #1 // Mint everything at once // VariableSupplyERC20Token could be used instead, but it needs to track available to mint supply (extra slot) contract FullPremintERC20Token is ERC20 { // uint constant _initialSupply = 100 * (10**18); constructor(string memory name_, string memory symbol_, uint256 supply_) ERC20(name_, symbol_) { require(supply_ > 0, "NO_ZERO_MINT"); _mint(_msgSender(), supply_); } }
Missing: @param name_
, @param symbol_
, @param supply_
File: contracts/token/VariableSupplyERC20Token.sol #2 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); }
Missing: @param account
, @param amount
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L19-L27
File: contracts/VTVLVesting.sol #3 /** @notice How many tokens are already allocated to vesting schedules. @dev Our balance of the token must always be greater than this amount. * Otherwise we risk some users not getting their shares. * This gets reduced as the users are paid out or when their schedules are revoked (as it is not reserved any more). * In other words, this represents the amount the contract is scheduled to pay out at some point if the * owner were to never interact with the contract. */ uint112 public numTokensReservedForVesting = 0;
Missing: @param numTokensReservedForVesting
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L414-L437
File: contracts/VTVLVesting.sol #4 /** @notice Allow an Owner to revoke a claim that is already active. @dev The requirement is that a claim exists and that it's active. */ function revokeClaim(address _recipient) external onlyAdmin hasActiveClaim(_recipient) { // Fetch the claim Claim storage _claim = claims[_recipient]; // Calculate what the claim should finally vest to uint112 finalVestAmt = finalVestedAmount(_recipient); // No point in revoking something that has been fully consumed // so require that there be unconsumed amount require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); // The amount that is "reclaimed" is equal to the total allocation less what was already withdrawn uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn; // Deactivate the claim, and release the appropriate amount of tokens _claim.isActive = false; // This effectively reduces the liability by amountRemaining, so we can reduce the liability numTokensReservedForVesting by that much numTokensReservedForVesting -= amountRemaining; // Reduces the allocation // Tell everyone a claim has been revoked. emit ClaimRevoked(_recipient, amountRemaining, uint40(block.timestamp), _claim); }
Missing: @param finalVestAmt
, @param amountRemaining
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L439-L451
File: contracts/VTVLVesting.sol #5 /** @notice Withdraw a token which isn't controlled by the vesting contract. @dev This contract controls/vests token at "tokenAddress". However, someone might send a different token. To make sure these don't get accidentally trapped, give admin the ability to withdraw them (to their own address). Note that the token to be withdrawn can't be the one at "tokenAddress". @param _otherTokenAddress - the token which we want to withdraw */ function withdrawOtherToken(IERC20 _otherTokenAddress) external onlyAdmin { require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); // tokenAddress address is already sure to be nonzero due to constructor uint256 bal = _otherTokenAddress.balanceOf(address(this)); require(bal > 0, "INSUFFICIENT_BALANCE"); _otherTokenAddress.safeTransfer(_msgSender(), bal); }
Missing: @param bal
There are 5 instances of this issue
Each event
should use three indexed
fields if there are three or more fields
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol#L14
File: contracts/AccessProtected.sol Line: 14 event AdminAccessSet(address indexed _admin, bool _enabled);
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 59: event ClaimCreated(address indexed _recipient, Claim _claim); 64: event Claimed(address indexed _recipient, uint112 _withdrawalAmount); 69: event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); 74: event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested);
🌟 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.3188 USDC - $9.32
Comparing to a constant (true
or false
) is a bit more expensive than directly checking the returned boolean value. I suggest using if(!directValue)
instead of if(directValue == false)
if (<x> == true)
=> if (<x>)
, if (<x> == false)
=> if (!<x>)
There is 1 instance of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L111
File: contracts/VTVLVesting.sol Line: 111 require(_claim.isActive == true, "NO_ACTIVE_CLAIM");
!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas). This change saves 6 gas per instance
There are 8 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 107: require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); 256: require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); 257: require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); 263: require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); 270-273: require( ( _cliffReleaseTimestamp > 0 && _cliffAmount > 0 && 449: require(bal > 0, "INSUFFICIENT_BALANCE");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/FullPremintERC20Token.sol#L11
File: contracts/token/FullPremintERC20Token.sol Line: 11 require(supply_ > 0, "NO_ZERO_MINT");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol
File: contracts/token/VariableSupplyERC20Token.sol 27: require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block: https://docs.soliditylang.org/en/v0.8.7/control-structures.html#checked-or-unchecked-arithmetic
There are 18 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 161: vestAmt += _claim.cliffAmount; 169: uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs / _claim.releaseIntervalSecs) * _claim.releaseIntervalSecs; 170: uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; 176: uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; 179: vestAmt += linearVestAmount; 217: return _baseVestedAmount(_claim, uint40(block.timestamp)) - _claim.amountWithdrawn; 256: require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); 264: require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); 292: uint112 allocatedAmount = _cliffAmount + _linearVestAmount; 295: require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); 301: numTokensReservedForVesting += allocatedAmount; 377: uint112 amountRemaining = allowance - usrClaim.amountWithdrawn; 381: usrClaim.amountWithdrawn += amountRemaining; 383: numTokensReservedForVesting -= amountRemaining; 400: uint256 amountRemaining = tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting; 429: uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn; 433: numTokensReservedForVesting -= amountRemaining;
File: contracts/token/VariableSupplyERC20Token.sol Line: 43 mintableSupply -= amount;
In Solidity 0.8+, there’s a default overflow check on unsigned integers. It’s possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline
Prior to Solidity 0.8.0, arithmetic operations would always wrap in case of under- or overflow leading to widespread use of libraries that introduce additional checks.
Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default, thus making the use of these libraries unnecessary.
To obtain the previous behaviour, an unchecked block can be used
There is 1 instance of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L353
File: contracts/VTVLVesting.sol Line: 353 353: for (uint256 i = 0; i < length; i++){}
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.
There are 3 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 27: uint112 public numTokensReservedForVesting = 0; 148: uint112 vestAmt = 0; 353: for (uint256 i = 0; i < length; i++) {
There are 7 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 161: vestAmt += _claim.cliffAmount; 179: vestAmt += linearVestAmount; 301: numTokensReservedForVesting += allocatedAmount; 381: usrClaim.amountWithdrawn += amountRemaining; 383: numTokensReservedForVesting -= amountRemaining; 433: numTokensReservedForVesting -= amountRemaining;
File: contracts/token/VariableSupplyERC20Token.sol Line: 43 mintableSupply -= amount;
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source: https://blog.soliditylang.org/2021/04/21/custom-errors/:
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g.,
revert("Insufficient funds.");
), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error
statement, which can be used inside and outside of contracts (including interfaces and libraries).
There are 24 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol
File: contracts/AccessProtected.sol 25: require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED"); 40: require(admin != address(0), "INVALID_ADDRESS");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 82: require(address(_tokenAddress) != address(0), "INVALID_ADDRESS"); 107: require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); 111: require(_claim.isActive == true, "NO_ACTIVE_CLAIM"); 129: require(_claim.startTimestamp == 0, "CLAIM_ALREADY_EXISTS"); 255: require(_recipient != address(0), "INVALID_ADDRESS"); 256: require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); 257: require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); 262: require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); 263: require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); 264: require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); 270-278: require( ( _cliffReleaseTimestamp > 0 && _cliffAmount > 0 && _cliffReleaseTimestamp <= _startTimestamp ) || ( _cliffReleaseTimestamp == 0 && _cliffAmount == 0 ), "INVALID_CLIFF"); 295: require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); 344-350: require(_startTimestamps.length == length && _endTimestamps.length == length && _cliffReleaseTimestamps.length == length && _releaseIntervalsSecs.length == length && _linearVestAmounts.length == length && _cliffAmounts.length == length, "ARRAY_LENGTH_MISMATCH" 374: require(allowance > usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW"); 402: require(amountRemaining >= _amountRequested, "INSUFFICIENT_BALANCE"); 426: require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); 447: require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); 449: require(bal > 0, "INSUFFICIENT_BALANCE");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/FullPremintERC20Token.sol#L11
File: contracts/token/FullPremintERC20Token.sol Line: 11 require(supply_ > 0, "NO_ZERO_MINT");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol
File: contracts/token/VariableSupplyERC20Token.sol 27: require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT"); 37: require(account != address(0), "INVALID_ADDRESS"); 41: require(amount <= mintableSupply, "INVALID_AMOUNT");
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.
There are 7 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol#L39
File: contracts/AccessProtected.sol Line: 39 function setAdmin(address admin, bool isEnabled) public onlyAdmin {
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 317-325: function createClaim( address _recipient, uint40 _startTimestamp, uint40 _endTimestamp, uint40 _cliffReleaseTimestamp, uint40 _releaseIntervalSecs, uint112 _linearVestAmount, uint112 _cliffAmount ) external onlyAdmin { 333-341: function createClaimsBatch( address[] memory _recipients, uint40[] memory _startTimestamps, uint40[] memory _endTimestamps, uint40[] memory _cliffReleaseTimestamps, uint40[] memory _releaseIntervalsSecs, uint112[] memory _linearVestAmounts, uint112[] memory _cliffAmounts) external onlyAdmin { 398: function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { 418: function revokeClaim(address _recipient) external onlyAdmin hasActiveClaim(_recipient) { 446: function withdrawOtherToken(IERC20 _otherTokenAddress) external onlyAdmin {
File: contracts/token/VariableSupplyERC20Token.sol Line: 36 function mint(address account, uint256 amount) public onlyAdmin {
msg.sender
costs 2 gas (CALLER opcode). _msgSender()
represents the following:
function _msgSender() internal view virtual returns (address payable) { return msg.sender;}
When no meta-transactions capabilities are used: msg.sender
is enough.
Consider replacing _msgSender()
with msg.sender
here
There are 13 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/AccessProtected.sol
File: contracts/AccessProtected.sol 17: _admins[_msgSender()] = true; 18: emit AdminAccessSet(_msgSender(), true); 25: require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED");
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 364: function withdraw() hasActiveClaim(_msgSender()) external { 367: Claim storage usrClaim = claims[_msgSender()]; 371: uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp)); 388: tokenAddress.safeTransfer(_msgSender(), amountRemaining); 391: emit Claimed(_msgSender(), amountRemaining); 407: tokenAddress.safeTransfer(_msgSender(), _amountRequested); 410: emit AdminWithdrawn(_msgSender(), _amountRequested); 450: _otherTokenAddress.safeTransfer(_msgSender(), bal);
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/FullPremintERC20Token.sol#L12
File: contracts/token/FullPremintERC20Token.sol Line: 12 _mint(_msgSender(), supply_);
File: contracts/token/VariableSupplyERC20Token.sol Line: 32 mint(_msgSender(), initialSupply_);
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.
EVM (Ethereum Virtual Machine) is driven by 256 bit (32 bytes) variables. This means that in our loop above, the uint16
variable is converted to a uint256
before it is used, this conversion costs gas.
There are 46 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 27: uint112 public numTokensReservedForVesting = 0; 35: uint40 startTimestamp; 36: uint40 endTimestamp; 37: uint40 cliffReleaseTimestamp; 38: uint40 releaseIntervalSecs; 42: uint112 linearVestAmount; 43: uint112 cliffAmount; 44: uint112 amountWithdrawn; 64: event Claimed(address indexed _recipient, uint112 _withdrawalAmount); 69: event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); 74: event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested); 147: function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { 148: uint112 vestAmt = 0; 167: uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; 169: uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs / _claim.releaseIntervalSecs) * _claim.releaseIntervalSecs; 170: uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; 176: uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; 196: function vestedAmount(address _recipient, uint40 _referenceTs) public view returns (uint112) { 206: function finalVestedAmount(address _recipient) public view returns (uint112) { 215: function claimableAmount(address _recipient) external view returns (uint112) { 217: return _baseVestedAmount(_claim, uint40(block.timestamp)) - _claim.amountWithdrawn; 247: uint40 _startTimestamp, 248: uint40 _endTimestamp, 249: uint40 _cliffReleaseTimestamp, 250: uint40 _releaseIntervalSecs, 251: uint112 _linearVestAmount, 252: uint112 _cliffAmount 292: uint112 allocatedAmount = _cliffAmount + _linearVestAmount; 319: uint40 _startTimestamp, 320: uint40 _endTimestamp, 321: uint40 _cliffReleaseTimestamp, 322: uint40 _releaseIntervalSecs, 323: uint112 _linearVestAmount, 324: uint112 _cliffAmount 335: uint40[] memory _startTimestamps, 336: uint40[] memory _endTimestamps, 337: uint40[] memory _cliffReleaseTimestamps, 338: uint40[] memory _releaseIntervalsSecs, 339: uint112[] memory _linearVestAmounts, 340: uint112[] memory _cliffAmounts) 371: uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp)); 377: uint112 amountRemaining = allowance - usrClaim.amountWithdrawn; 398: function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { 422: uint112 finalVestAmt = finalVestedAmount(_recipient); 429: uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn; 436: emit ClaimRevoked(_recipient, amountRemaining, uint40(block.timestamp), _claim);
There are 2 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 154: if(_referenceTs > _claim.endTimestamp) { 166: if(_referenceTs > _claim.startTimestamp) {
There are 2 instances of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol
File: contracts/VTVLVesting.sol 270-277: require( ( _cliffReleaseTimestamp > 0 && _cliffAmount > 0 && _cliffReleaseTimestamp <= _startTimestamp ) || ( _cliffReleaseTimestamp == 0 && _cliffAmount == 0 344-349: require(_startTimestamps.length == length && _endTimestamps.length == length && _cliffReleaseTimestamps.length == length && _releaseIntervalsSecs.length == length && _linearVestAmounts.length == length && _cliffAmounts.length == length,
Prefix increments are cheaper than postfix increments.
Further more, using unchecked {++i} is even more gas efficient, and the gas saving accumulates every iteration and can make a real change
There is no risk of overflow caused by increamenting the iteration index in for loops (the ++i
in for (uint256 i = 0; i < length; ++i) {
).
But increments perform overflow checks that are not necessary in this case.
These functions use not using prefix increments (++i
) or not using the unchecked keyword
There is 1 instance of this issue
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L353
File: contracts/VTVLVesting.sol Line:353 for (uint256 i = 0; i < length; i++) {