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: 9/198
Findings: 4
Award: $794.92
🌟 Selected for report: 1
🚀 Solo Findings: 0
218.0935 USDC - $218.09
It is not ideal for admins to be able to withdraw any vested amount that is ALREADY claimable by vesting recipients to keep vesting recipient's (or investors or employees) incentives alligned with admins project. The point I want to say is that, current revoke function is possible for admins to revoke the claim even AFTER the vested amount schedule is reached. This means that recipients technically lost their funds that was available, if they didn't withdraw before it was revoked by admins and admins can now withdraw that amount for themselves instead. This is too much power given to admin and not ideal for vesting token process.
I will explain in more details by referencing some of VTVL team documents from the contest page.
At the VTVL contest page it is stated as
due of the nature of vesting, e.g. employee token vesting, our vesting contract is deliberately designed to allow admin revocation in the circumstances of early employment termination (before the end of vesting period specified).
If the project intention is to revoke the claim for employees who is terminated, then current revoke function has too much power than it should be.
Because the current revokeClaim
function in VTVLVesting.sol
contract can revoke the claim even though there is already valid vested amount claimable
by recipients, meaning that if recipients didn't withdraw their claimable vested amount before the admin execute revokeClaim
function, they will loss their vested amount. This revokeClaim
function should instead just close the remaining future vesting schedule and allow recipients to withdraw any vested amount token that already reached the schedule.
Vesting recipients If an user has a valid claim associated to their address, they have the ability to withdraw the amount that's claimable at the moment they make the claim. No one other than the designated vesting recipient (not even the admin) can withdraw on an existing claim - but the admin can revoke the claim.
Also above statement of No one other than the designated vesting recipient (not even the admin) can withdraw on an existing claim
about
vesting recipients is not true because admin can technically withdraw on an existing claim
because admin can revoke
claim anytime they want
and if the vested amount is not yet withdrawn by vesting recipients, this amount will be deducted from numTokensReservedForVesting
variable,
which means that recipients can no longer withdraw the vested amount and admins can withdraw instead, which is technically same as admin
withdraw on an existing claim
.
Manual Analysis
It is hard to implement above logic without changing too much of existing code but below example might be the simplest way.
For revokeClaim
function, keep line 419-420, and swap remaining line 421-436 with below code.
Below code is checking whether current time is not passed the _claim.endTimestamp
and if there is any vested amount
that is not yet withdrawn by recipient. If there is, this amount will sent to recipient and remaining future amount will
be back with the VTVLVesting contract and close the claim.
// No point in revoking after the vesting schedule is finished require(uint40(block.timestamp) < _claim.endTimestamp, "VESTING_ENDED"); // Calculate what the claim should finally vest to uint112 finalVestAmt = finalVestedAmount(_recipient); // Calculating vested amount of recipient at current time uint112 currentVestAmt = vestedAmount(_recipient, uint40(block.timestamp)); // Check whether recipient has any claimable vested amount that is not withdrawn yet if (currentVestAmt > _claim.amountWithdrawn) { // Vested amount that recipient can claim but not yet withdrawn uint112 amountRemainingForRecipient = currentVestAmt - _claim.amountWithdrawn; // Vested amount that was scheduled in future but now will be revoked uint112 amountRemaining = finalVestAmt - currentVestAmt; // Deactivate the claim, and release the appropriate amount of tokens _claim.isActive = false; numTokensReservedForVesting -= amountRemaining; // Send already claimable vested amount that is not withdrawn to recipient and close the claim tokenAddress.safeTransfer(_recipient, amountRemainingForRecipient); emit Claimed(_recipient, amountRemainingForRecipient); } else { uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn; _claim.isActive = false; numTokensReservedForVesting -= amountRemaining; } emit ClaimRevoked(_recipient, amountRemaining, uint40(block.timestamp), _claim);
However this might now open to new vulnerability such as recipient selling their future claimable vesting token by creating malicious contract and making that contract as an recipient. This can be prevented by checking whether vesting recipients is EOA and not a contract. Reference: https://blog.openzeppelin.com/bypassing-smart-contract-timelocks/
#0 - 0xean
2022-09-24T19:03:09Z
dupe of #475
🌟 Selected for report: TomJ
Also found by: ayeslick, csanuragjain, pashov
548.864 USDC - $548.86
There is no check whether _startTimestamp
and _endTimestamp
is greater than block.timestamp
at VTVLVesting.sol
_createClaimUnchecked
function. Therefore it is possible for administrators to accidentally create
vesting schedule that starts and ends in the past without noticing it. When administrators does this and this transaction goes
through, then the vesting recipients can withdraw their entire vest amount which is not what administrators intended to do.
Add require check that force _startTimestamp
to be greater than block.timestamp
.
Team comments as below on line 260
// -> Conclusion: we want to allow this, for founders that might have forgotten to add some users, or to avoid issues with transactions not going through because of discoordination between block.timestamp and sender's local time
However this is not an issue by adding require(_startTimestamp > uint40(block.timestamp))
since this will revert
transaction if _startTimestamp is less than block.timestamp so administrators can simply try again with correct time.
On the other hand, it is more dangerous to not include this check because transaction will simply succeed even though
_startTimestamp is set to past which means that there is a chance of administrators not noticing this.
createClaim
function. However admin mistakenly set _startTimestamp
and
_endTimestamp
in the past.require(_startTimestamp > uint40(block.timestamp))
, this transaction is valid and claim
is created.withdraw
function and receive entire vest amount.Manual Analysis
Add following check in VTVLVesting.sol:_createClaimUnchecked
function.
require(_startTimestamp > uint40(block.timestamp), "INVALID_START_TIMESTAMP")
#0 - 0xean
2022-09-25T19:02:57Z
Going to use this issue for encompassing a few different reports that all revolve around adding some better validation around timestamps. These include a few different potential fixes that the sponsor can review, but ultimately point to the same underlying issues.
#1 - lawrencehui
2022-10-07T01:40:25Z
As described in the documentation, this back dated (startTimestamp < block.timestamp) feature is indeed intended as there are many real life cases that founders want to reward their employees in the way the vesting period starts well before Token Generation Event (TGE).
We appreciate wardens' feedback on additional checking (both start and end time) and in our actual application, we would include multiple layer of checking / approval processes in front and backend before the transaction signing happens and therefore the risk is low in our opinion.
🌟 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.8577 USDC - $18.86
Context
inheritance 
There might be an occasion where administrators (founders) wants to deploy the token vesting with same token again.
In currenct VTVLVesting.sol contract, it is not possible to assign same address as vesting recipients even though the
vesting schedule is finished (and all of its vest amount is withdrawn) because claim.isActive
is always true
once a claim is created (It will only be false again if admin execute revokeClaim()
before all of its vest amount is withdrawn).
Therefore it might be ideal to set claim.isActive
to false once all of the vest amount is withdrawn.
Add below code after line 384 to make claim not active after all its vest amount is withdrawn https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L364-L392
uint112 finalVestAmt = finalVestedAmount(_msgSender()); if ( usrClaim.amountWithdrawn == finalVestAmt ) { usrClaim.isActive = false; }
 
Context
inheritanceContext
inheritance is unnecessary for VTVLVesting.sol
since it is already inherited at AccessProtected.sol
.
VTVLVesting.sol: contract VTVLVesting is Context, AccessProtected {
AccessProtected.sol: abstract contract AccessProtected is Context {
Remove Context
from VTVLVesting.sol
like shown in below example.
Example: contract VTVLVesting is AccessProtected {
 
it is best practice to lock your pragma instead of using floating pragma. the use of floating pragma has a risk of accidentally get deployed using latest complier which may have higher risk of undiscovered bugs. Reference: https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/locking-pragmas/
./VariableSupplyERC20Token.sol:2:pragma solidity ^0.8.14;
I suggest to lock your pragma and aviod using floating pragma.
// bad pragma solidity ^0.8.10; // good pragma solidity 0.8.10;
 
Each event should have 3 indexed fields if there are 3 or more fields.
./VTVLVesting.sol:59: event ClaimCreated(address indexed _recipient, Claim _claim); ./VTVLVesting.sol:64: event Claimed(address indexed _recipient, uint112 _withdrawalAmount); ./VTVLVesting.sol:69: event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); ./VTVLVesting.sol:74: event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested); ./AccessProtected.sol:14: event AdminAccessSet(address indexed _admin, bool _enabled);
Add up to 3 indexed fields when possible.
 
🌟 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
Total of 10 issues found.
 
Since Solidity 0.8.0, all arithmetic operations revert on overflow and underflow by default. However in places where overflow and underflow is not possible, it is better to use unchecked block to reduce the gas usage. Reference: https://docs.soliditylang.org/en/v0.8.15/control-structures.html#checked-or-unchecked-arithmetic
Total of 7 instances found.
_baseVestedAmount()
of VTVLVesting.sol
(line 167)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L166-L167Wrap line 167 with unchecked since underflow is not possible due to line 166 check if(_referenceTs > _claim.startTimestamp) { uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start
_baseVestedAmount()
of VTVLVesting.sol
(line 170)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L170Wrap line 170 with unchecked since underflow is not possible due to line 262 check 262: require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp
170: uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; // length of the interval
_createClaimUnchecked()
of VTVLVesting.sol
(line 264)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L264Wrap line 264 with unchecked since underflow is not possible due to line 262 check 262: require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp 264: require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH");
withdraw()
of VTVLVesting.sol
(line 377)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L374-L377Wrap line 377 with unchecked since underflow is not possible due to line 374 check 374: require(allowance > usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW"); 377: uint112 amountRemaining = allowance - usrClaim.amountWithdrawn;
withdrawAdmin()
of VTVLVesting.sol
(line 400)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L400Wrap line 400 with unchecked since underflow is not possible due to line 295 check 400: uint256 amountRemaining = tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting;
295: require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE");
revokeClaim()
of VTVLVesting.sol
(line 429)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L426-L429Wrap line 429 with unchecked since underflow is not possible due to line 426 check 426: require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); 429: uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn;
mint()
of VariableSupplyERC20Token.sol
(line 43)
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/token/VariableSupplyERC20Token.sol#L41-L43Wrap line 43 with unchecked since underflow is not possible due to line 41 check 41: require(amount <= mintableSupply, "INVALID_AMOUNT"); 43: mintableSupply -= amount;
Wrap the code with uncheck like described in above PoC.
 
Certain variables is defined even though they are used only once. Remove these unnecessary variables to save gas. For cases where it will reduce the readability, one can use comments to help describe what the code is doing.
Total of 2 instances found.
vestedAmount()
of VTVLVesting.sol
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L197-L198
Mitigation:
Delete line 197 and replace line 198 with below codereturn _baseVestedAmount(claims[_recipient], _referenceTs);
withdrawAdmin()
of VTVLVesting.sol
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L400-L402
Mitigation:
Delete line 400 and replace line 402 with below coderequire(tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting >= _amountRequested, "INSUFFICIENT_BALANCE");
Don't define variable that is used only once. Details are listed on above PoC.
 
X = X + Y costs less gass than X += Y for state variables
Total of 1 instance found.
numTokensReservedForVesting
variable of VTVLVesting.sol
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L301numTokensReservedForVesting += allocatedAmount; // track the allocated amount
Change it to
numTokensReservedForVesting = numTokensReservedForVesting + allocatedAmount; // track the allocated amount
It saves 8 gas Average gas before modificaiton: 167760 Average gas after modificaiton: 167752
 
When there are multiple conditions in require statement, break down the require statement into multiple require statements instead of using && can save gas.
Total of 1 instance found.
./VTVLVesting.sol:344: require(_startTimestamps.length == length && _endTimestamps.length == length && _cliffReleaseTimestamps.length == length && _releaseIntervalsSecs.length == length && _linearVestAmounts.length == length && _cliffAmounts.length == length, "ARRAY_LENGTH_MISMATCH"
Break down into several require statement. For example,
require(_startTimestamps.length == length); require(_endTimestamps.length == length); require(_cliffreleasetimestamps.length == length); require(_releaseIntervalsSecs.length == length); require(_linearVestAmounts.length == length); require(_cliffAmounts.length == length, "ARRAY_LENGTH_MISMATCH");
 
If the function is not called internally, it is cheaper to set your function visibility to external instead of public.
Total of 2 instances found.
VTVLVesting.sol:withdrawAdmin() https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L398
AccessProtected.sol:setAdmin() https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/AccessProtected.sol#L39
Change the function visibility to external.
 
It is cheaper gas to use calldata than memory if the function parameter is read only. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. More details on following link. link: https://docs.soliditylang.org/en/v0.8.15/types.html#data-location
Total of 7 instances found.
./VTVLVesting.sol:334: address[] memory _recipients, ./VTVLVesting.sol:335: uint40[] memory _startTimestamps, ./VTVLVesting.sol:336: uint40[] memory _endTimestamps, ./VTVLVesting.sol:337: uint40[] memory _cliffReleaseTimestamps, ./VTVLVesting.sol:338: uint40[] memory _releaseIntervalsSecs, ./VTVLVesting.sol:339: uint112[] memory _linearVestAmounts, ./VTVLVesting.sol:340: uint112[] memory _cliffAmounts)
Change memory to calldata
 
It is more gas expensive to compare boolean with "variable == true" or "variable == false" than directly checking the returned boolean value.
Total of 1 instance found.
./VTVLVesting.sol:111: require(_claim.isActive == true, "NO_ACTIVE_CLAIM");
Simply check by returned boolean value. Change it to
require(_claim.isActive, "NO_ACTIVE_CLAIM");
 
Since EVM operates on 32 bytes at a time, if the element is smaller than that, the EVM must use more operations in order to reduce the elements from 32 bytes to specified size. Therefore it is more gas saving to use 32 bytes unless it is used in a struct or state variable in order to reduce storage slot.
Reference: https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html
Total of 18 instances found.
./VTVLVesting.sol:27: uint112 public numTokensReservedForVesting = 0; ./VTVLVesting.sol:64: event Claimed(address indexed _recipient, uint112 _withdrawalAmount); ./VTVLVesting.sol:74: event AdminWithdrawn(address indexed _recipient, uint112 _amountRequested); ./VTVLVesting.sol:147: function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { ./VTVLVesting.sol:148: uint112 vestAmt = 0; ./VTVLVesting.sol:167: uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start ./VTVLVesting.sol:169: uint40 truncatedCurrentVestingDurationSecs = (currentVestingDurationSecs / _claim.releaseIntervalSecs) * _claim.releaseIntervalSecs; ./VTVLVesting.sol:170: uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; // length of the interval ./VTVLVesting.sol:176: uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; ./VTVLVesting.sol:196: function vestedAmount(address _recipient, uint40 _referenceTs) public view returns (uint112) { ./VTVLVesting.sol:206: function finalVestedAmount(address _recipient) public view returns (uint112) { ./VTVLVesting.sol:215: function claimableAmount(address _recipient) external view returns (uint112) { ./VTVLVesting.sol:292: uint112 allocatedAmount = _cliffAmount + _linearVestAmount; ./VTVLVesting.sol:371: uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp)); ./VTVLVesting.sol:377: uint112 amountRemaining = allowance - usrClaim.amountWithdrawn; ./VTVLVesting.sol:398: function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { ./VTVLVesting.sol:422: uint112 finalVestAmt = finalVestedAmount(_recipient); ./VTVLVesting.sol:429: uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn;
I suggest using uint256 instead of anything smaller and downcast where needed.
 
Prefix increments/decrements (++i or --i) costs cheaper gas than postfix increment/decrements (i++ or i--).
Total of 1 instance found.
./VTVLVesting.sol:353: for (uint256 i = 0; i < length; i++) {
Change it to postfix increments/decrements. It saves 6 gas per loop. For example,
for (uint256 i = 0; i < length; ++i) {
 
Custom errors from Solidity 0.8.4 are cheaper than revert strings. Details are explained here: https://blog.soliditylang.org/2021/04/21/custom-errors/
Total of 24 instances found.
./VTVLVesting.sol:82: require(address(_tokenAddress) != address(0), "INVALID_ADDRESS"); ./VTVLVesting.sol:107: require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); ./VTVLVesting.sol:111: require(_claim.isActive == true, "NO_ACTIVE_CLAIM"); ./VTVLVesting.sol:129: require(_claim.startTimestamp == 0, "CLAIM_ALREADY_EXISTS"); ./VTVLVesting.sol:255: require(_recipient != address(0), "INVALID_ADDRESS"); ./VTVLVesting.sol:256: require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both ./VTVLVesting.sol:257: require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); ./VTVLVesting.sol:262: require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp ./VTVLVesting.sol:263: require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); ./VTVLVesting.sol:264: require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); ./VTVLVesting.sol:270: require( ( _cliffReleaseTimestamp > 0 && _cliffAmount > 0 && _cliffReleaseTimestamp <= _startTimestamp ) || ( _cliffReleaseTimestamp == 0 && _cliffAmount == 0 ), "INVALID_CLIFF"); ./VTVLVesting.sol:295: require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); ./VTVLVesting.sol:344: require(_startTimestamps.length == length && _endTimestamps.length == length && _cliffReleaseTimestamps.length == length && _releaseIntervalsSecs.length == length && _linearVestAmounts.length == length && _cliffAmounts.length == length, "ARRAY_LENGTH_MISMATCH" ); ./VTVLVesting.sol:374: require(allowance > usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW"); ./VTVLVesting.sol:402: require(amountRemaining >= _amountRequested, "INSUFFICIENT_BALANCE"); ./VTVLVesting.sol:426: require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); ./VTVLVesting.sol:447: require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); // tokenAddress address is already sure to be nonzero due to constructor ./VTVLVesting.sol:449: require(bal > 0, "INSUFFICIENT_BALANCE"); ./FullPremintERC20Token.sol:11: require(supply_ > 0, "NO_ZERO_MINT"); ./VariableSupplyERC20Token.sol:27: require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT"); ./VariableSupplyERC20Token.sol:37: require(account != address(0), "INVALID_ADDRESS"); ./VariableSupplyERC20Token.sol:41: require(amount <= mintableSupply, "INVALID_AMOUNT"); ./AccessProtected.sol:25: require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED"); ./AccessProtected.sol:40: require(admin != address(0), "INVALID_ADDRESS");
I suggest implementing custom errors to save gas.