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: 49/198
Findings: 3
Award: $75.75
š Selected for report: 0
š Solo Findings: 0
š Selected for report: rajatbeladiya
Also found by: 0x4non, CertoraInc, Chom, JLevick, JohnSmith, KIntern_NA, Ruhum, RustyRabbit, ak1, berndartmueller, imare, joestakey, obront, rbserver, rotcivegaf, supernova
32.8268 USDC - $32.83
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L123-L140 https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L253
Will need create an instance of the contract per recipient if need a different claims for the same recipient Also when finish/revoke a claim cannot create one new with the same recipient
) private hasNoClaim(_recipient) {
/** @notice Modifier which is opposite hasActiveClaim @dev Requires that all fields are unset */ modifier hasNoClaim(address _recipient) { Claim storage _claim = claims[_recipient]; // Start timestamp != 0 is a sufficient condition for a claim to exist // This is because we only ever add claims (or modify startTs) in the createClaim function // Which requires that its input startTimestamp be nonzero // So therefore, a zero value for this indicates the claim does not exist. require(_claim.startTimestamp == 0, "CLAIM_ALREADY_EXISTS"); // We don't even need to check for active to be unset, since this function only // determines that a claim hasn't been set // require(_claim.isActive == false, "CLAIM_ALREADY_EXISTS"); // Further checks aren't necessary (to save gas), as they're done at creation time (createClaim) // require(_claim.endTimestamp == 0, "CLAIM_ALREADY_EXISTS"); // require(_claim.linearVestAmount + _claim.cliffAmount == 0, "CLAIM_ALREADY_EXISTS"); // require(_claim.amountWithdrawn == 0, "CLAIM_ALREADY_EXISTS"); _; }
Review
Use ERC721 EIP to manage the claims, where the ownership goes from id to owner(claimId => recipient)
This give the possibility to get N claims
to N recipients
, transfer claims, offchain monitoring, etc
#0 - 0xean
2022-09-24T21:42:09Z
dupe of #140
š 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
L-N | Issue | Instances |
---|---|---|
[Lā01] | Open question, the response could change the contract behavior | 1 |
Total: 1 instances over 1 issues
N-N | Issue | Instances |
---|---|---|
[Nā01] | Unused imports | 2 |
[Nā02] | Non-library/interface files should use fixed compiler versions, not floating ones | 1 |
[Nā03] | Should be mock contracts | 2 |
[Nā04] | Unused commented code lines | 5 |
[Nā05] | Lint | 17 |
[Nā06] | Declare the return variable in the returns | 1 |
[Nā07] | Add the ability to withdraw ETH | 1 |
[Nā08] | Open TODO | 1 |
[Nā09] | Lines are too long | 66 |
[Nā10] | Typo | 1 |
Total: 97 instances over 10 issues
It's an open question: Should we allow this?
, but don't allow the scenario of initialSupply_
and maxSupply_
, both are 0
File: /contracts/token/VariableSupplyERC20Token.sol 22 // max supply == 0 means mint at will. 23 // initialSupply_ == 0 means nothing preminted 24 // Therefore, we have valid scenarios if either of them is 0 25 // However, if both are 0 we might have a valid scenario as well - user just wants to create a token but doesn't want to mint anything 26 // Should we allow this?
File: /contracts/AccessProtected.sol 05 import "@openzeppelin/contracts/access/Ownable.sol";
File: /contracts/VTVLVesting.sol 06 import "@openzeppelin/contracts/access/Ownable.sol";
File: /contracts/token/VariableSupplyERC20Token.sol 2 pragma solidity ^0.8.14;
The /contracts/token/FullPremintERC20Token.sol and /contracts/token/VariableSupplyERC20Token.sol are used as helpers for development, move to test folder and name with Mock at start like: MockFullPremintERC20Token.sol and MockVariableSupplyERC20Token.sol
There are a lot of commented code lines with this code could be important for the contract behavior
File: /contracts/token/FullPremintERC20Token.sol 9 // uint constant _initialSupply = 100 * (10**18);
File: /contracts/VTVLVesting.sol 113 // Save gas, omit further checks 114 // require(_claim.linearVestAmount + _claim.cliffAmount > 0, "INVALID_VESTED_AMOUNT"); 115 // require(_claim.endTimestamp > 0, "NO_END_TIMESTAMP"); 131 // We don't even need to check for active to be unset, since this function only 132 // determines that a claim hasn't been set 133 // require(_claim.isActive == false, "CLAIM_ALREADY_EXISTS"); 135 // Further checks aren't necessary (to save gas), as they're done at creation time (createClaim) 136 // require(_claim.endTimestamp == 0, "CLAIM_ALREADY_EXISTS"); 137 // require(_claim.linearVestAmount + _claim.cliffAmount == 0, "CLAIM_ALREADY_EXISTS"); 138 // require(_claim.amountWithdrawn == 0, "CLAIM_ALREADY_EXISTS"); 261 // require(_endTimestamp > 0, "_endTimestamp must be valid"); // not necessary because of the next condition (transitively)
Add space between if
and (
, if (
:
File: /contracts/token/VariableSupplyERC20Token.sol 31 if(initialSupply_ > 0) { 40 if(mintableSupply > 0) {
File: /contracts/VTVLVesting.sol 151 if(_claim.isActive) { 154 if(_referenceTs > _claim.endTimestamp) { 160 if(_referenceTs >= _claim.cliffReleaseTimestamp) { 166 if(_referenceTs > _claim.startTimestamp) {
Remove space:
File: /contracts/VTVLVesting.sol From: 426 ) private hasNoClaim(_recipient) { To: 426 ) private hasNoClaim(_recipient) { From: 97 @dev To determine this, we check that a claim: To: 97 @dev To determine this, we check that a claim: From: 426 require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); To: 426 require(_claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); 331 \n 366 \n 413 \n
Wrong identation:
File: /contracts/VTVLVesting.sol From: 246 address _recipient, 247 uint40 _startTimestamp, 248 uint40 _endTimestamp, 249 uint40 _cliffReleaseTimestamp, 250 uint40 _releaseIntervalSecs, 251 uint112 _linearVestAmount, 252 uint112 _cliffAmount 253 ) private hasNoClaim(_recipient) { To: 246 address _recipient, 247 uint40 _startTimestamp, 248 uint40 _endTimestamp, 249 uint40 _cliffReleaseTimestamp, 250 uint40 _releaseIntervalSecs, 251 uint112 _linearVestAmount, 252 uint112 _cliffAmount 253 ) private hasNoClaim(_recipient) { From: 270 require( 271 ( 272 _cliffReleaseTimestamp > 0 && 273 _cliffAmount > 0 && 274 _cliffReleaseTimestamp <= _startTimestamp 275 ) || ( 276 _cliffReleaseTimestamp == 0 && 277 _cliffAmount == 0 278 ), "INVALID_CLIFF"); To: 270 require( 271 ( 272 _cliffReleaseTimestamp > 0 && 273 _cliffAmount > 0 && 274 _cliffReleaseTimestamp <= _startTimestamp 275 ) || ( 276 _cliffReleaseTimestamp == 0 && 277 _cliffAmount == 0 278 ), 279 "INVALID_CLIFF" 280 ); From: 318 address _recipient, 319 uint40 _startTimestamp, 320 uint40 _endTimestamp, 321 uint40 _cliffReleaseTimestamp, 322 uint40 _releaseIntervalSecs, 323 uint112 _linearVestAmount, 324 uint112 _cliffAmount 325 ) external onlyAdmin { To: 318 address _recipient, 319 uint40 _startTimestamp, 320 uint40 _endTimestamp, 321 uint40 _cliffReleaseTimestamp, 322 uint40 _releaseIntervalSecs, 323 uint112 _linearVestAmount, 324 uint112 _cliffAmount 325 ) external onlyAdmin { From: 340 uint112[] memory _cliffAmounts) 341 external onlyAdmin { To: 340 uint112[] memory _cliffAmounts 341 ) external onlyAdmin { From: 344 require(_startTimestamps.length == length && 345 _endTimestamps.length == length && 346 _cliffReleaseTimestamps.length == length && 347 _releaseIntervalsSecs.length == length && 348 _linearVestAmounts.length == length && 349 _cliffAmounts.length == length, 350 "ARRAY_LENGTH_MISMATCH" 351 ); To: 344 require( 345 _startTimestamps.length == length && 346 _endTimestamps.length == length && 347 _cliffReleaseTimestamps.length == length && 348 _releaseIntervalsSecs.length == length && 349 _linearVestAmounts.length == length && 350 _cliffAmounts.length == length, 351 "ARRAY_LENGTH_MISMATCH" 352 );
File: /contracts/VTVLVesting.sol From: 147 function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { 148 uint112 vestAmt = 0; To: 147 function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112 vestAmt) {
The comment of the withdrawOtherToken
function says: "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)."
The contract must also have a function to withdraw ETH in case someone sends ETH
Open TODO can point to architecture or programming issues that still need to be resolved.
File: /contracts/VTVLVesting.sol From: 266 // Potential TODO: sanity check, if _linearVestAmount == 0, should we perhaps force that start and end ts are the same?
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length
Over 100 characters:
File: /contracts/token/FullPremintERC20Token.sol 7 // VariableSupplyERC20Token could be used instead, but it needs to track available to mint supply (extra slot) 10 constructor(string memory name_, string memory symbol_, uint256 supply_) ERC20(name_, symbol_) {
File: /contracts/token/VariableSupplyERC20Token.sol 8 @notice A ERC20 token contract that allows minting at will, with limited or unlimited supply. 14 @notice A ERC20 token contract that allows minting at will, with limited or unlimited supply. No burning possible 18 @param initialSupply_ - How much to immediately mint (saves another transaction). If 0, no mint at the beginning. 19 @param maxSupply_ - What's the maximum supply. The contract won't allow minting over this amount. Set to 0 for no limit. 21 constructor(string memory name_, string memory symbol_, uint256 initialSupply_, uint256 maxSupply_) ERC20(name_, symbol_) { 25 // However, if both are 0 we might have a valid scenario as well - user just wants to create a token but doesn't want to mint anything 30 // Note: the check whether initial supply is less than or equal than mintableSupply will happen in mint fn. 49 // Example: if the user can burn tokens already assigned to vesting schedules, it could be unable to pay its obligations.
File: /contracts/AccessProtected.sol 9 @dev Exposes the onlyAdmin modifier, which will revert (ADMIN_ACCESS_REQUIRED) if the caller is not the owner nor the admin.
File: /contracts/VTVLVesting.sol 23 * This gets reduced as the users are paid out or when their schedules are revoked (as it is not reserved any more). 24 * In other words, this represents the amount the contract is scheduled to pay out at some point if the 34 // Gives us a range from 1 Jan 1970 (Unix epoch) up to approximately 35 thousand years from then (2^40 / (365 * 24 * 60 * 60) ~= 35k) 36 uint40 endTimestamp; // When does the vesting end - the vesting goes linearly between the start and end timestamps 37 uint40 cliffReleaseTimestamp; // At which timestamp is the cliffAmount released. This must be <= startTimestamp 44 uint112 amountWithdrawn; // how much was withdrawn thus far - released at the cliffReleaseTimestamp 69 event ClaimRevoked(address indexed _recipient, uint112 _numTokensWithheld, uint256 revocationTimestamp, Claim _claim); 88 @dev Could be using public claims var, but this is cleaner in terms of naming. (getClaim(address) as opposed to claims(address)). 135 // Further checks aren't necessary (to save gas), as they're done at creation time (createClaim) 143 @notice Pure function to calculate the vested amount from a given _claim, at a reference timestamp 147 function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { 164 // Calculate the linearly vested amount - this is relevant only if we're past the schedule start 165 // at _referenceTs == _claim.startTimestamp, the period proportion will be 0 so we don't need to start the calc 167 uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start 172 // Calculate the linear vested amount - fraction_of_interval_completed * linearVestedAmount 173 // Since fraction_of_interval_completed is truncatedCurrentVestingDurationSecs / finalVestingDurationSecs, the formula becomes 174 // truncatedCurrentVestingDurationSecs / finalVestingDurationSecs * linearVestAmount, so we can rewrite as below to avoid 176 uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; 184 // Rationale: no matter how we calculate the vestAmt, we can never return that less was vested than was already withdrawn. 185 // Case where this could be relevant - If the claim is inactive, vestAmt would be 0, yet if something was already withdrawn 186 // on that claim, we want to return that as vested thus far - as we want the function to be monotonic. 202 @notice Calculate the total vested at the end of the schedule, by simply feeding in the end timestamp to the function above. 212 @notice Calculates how much can we claim, by subtracting the already withdrawn amount from the vestedAmount at this moment. 235 @notice Permission-unchecked version of claim creation (no onlyAdmin). Actual logic for create claim, to be run within either createClaim or createClaimBatch. 236 @dev This'll simply check the input parameters, and create the structure verbatim based on passed in parameters. 240 @param _cliffReleaseTimestamp - The timestamp when the cliff is released (must be <= _startTimestamp, or 0 if no vesting) 241 @param _releaseIntervalSecs - The release interval for the linear vesting. If this is, for example, 60, that means that the linearly vested amount gets released every 60 seconds. 242 @param _linearVestAmount - The total amount to be linearly vested between _startTimestamp and _endTimestamp 243 @param _cliffAmount - The amount released at _cliffReleaseTimestamp. Can be 0 if _cliffReleaseTimestamp is also 0. 256 require(_linearVestAmount + _cliffAmount > 0, "INVALID_VESTED_AMOUNT"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both 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 261 // require(_endTimestamp > 0, "_endTimestamp must be valid"); // not necessary because of the next condition (transitively) 262 require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp 264 require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); 266 // Potential TODO: sanity check, if _linearVestAmount == 0, should we perhaps force that start and end ts are the same? 269 // Both or neither of _cliffReleaseTimestamp and _cliffAmount must be set. If cliff is set, _cliffReleaseTimestamp must be before or at the _startTimestamp 290 // Our total allocation is simply the full sum of the two amounts, _cliffAmount + _linearVestAmount 294 // Still no effects up to this point (and tokenAddress is selected by contract deployer and is immutable), so no reentrancy risk 295 require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); 208 @dev This'll simply check the input parameters, and create the structure verbatim based on passed in parameters. 312 @param _cliffReleaseTimestamp - The timestamp when the cliff is released (must be <= _startTimestamp, or 0 if no vesting) 313 @param _releaseIntervalSecs - The release interval for the linear vesting. If this is, for example, 60, that means that the linearly vested amount gets released every 60 seconds. 314 @param _linearVestAmount - The total amount to be linearly vested between _startTimestamp and _endTimestamp 315 @param _cliffAmount - The amount released at _cliffReleaseTimestamp. Can be 0 if _cliffReleaseTimestamp is also 0. 326 _createClaimUnchecked(_recipient, _startTimestamp, _endTimestamp, _cliffReleaseTimestamp, _releaseIntervalSecs, _linearVestAmount, _cliffAmount); 330 @notice The batch version of the createClaim function. Each argument is an array, and this function simply repeatedly calls the createClaim. 354 _createClaimUnchecked(_recipients[i], _startTimestamps[i], _endTimestamps[i], _cliffReleaseTimestamps[i], _releaseIntervalsSecs[i], _linearVestAmounts[i], _cliffAmounts[i]); 369 // we can use block.timestamp directly here as reference TS, as the function itself will make sure to cap it to endTimestamp 382 // Reduce the allocated amount since the following transaction pays out so the "debt" gets reduced 400 uint256 amountRemaining = tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting; 428 // The amount that is "reclaimed" is equal to the total allocation less what was already withdrawn 432 _claim.isActive = false; // This effectively reduces the liability by amountRemaining, so we can reduce the liability numTokensReservedForVesting by that much 441 @dev This contract controls/vests token at "tokenAddress". However, someone might send a different token. 442 To make sure these don't get accidentally trapped, give admin the ability to withdraw them (to their own address). 446 require(_otherTokenAddress != tokenAddress, "INVALID_TOKEN"); // tokenAddress address is already sure to be nonzero due to constructor
File: /contracts/token/VariableSupplyERC20Token.sol From: 107 // Next, we need to calculated the duration truncated to nearest releaseIntervalSecs To: 107 // Next, we need to calculate the duration truncated to nearest releaseIntervalSecs
š 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
G-N | Issue | Instances |
---|---|---|
[Gā01] | No need to initialize variables with default values | 3 |
[Gā02] | Use custom errors rather than revert() /require() strings to save gas | 24 |
[Gā03] | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) | 1 |
[Gā04] | ++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 | 1 |
[Gā05] | Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if -statement | 6 |
[Gā06] | Overcheck if the account it is not address(0) | 1 |
[Gā07] | The use of _msgSender() when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumption | 13 |
[Gā08] | public functions to external functions | 2 |
[Gā09] | Using calldata instead of memory for read-only arguments in external functions saves gas | 1 |
[Gā10] | Use ClaimCreated event to track the recipients of the vesting instead of storage in vestingRecipients array | 1 |
[Gā11] | Redundant type casting | 1 |
[Gā12] | Redundant check | 1 |
[Gā13] | Overcalculate finalVestedAmount | 1 |
Total: 56 instances over 13 issues
In solidity all variables initialize in 0, address(0), false, etc.
File: /contracts/VTVLVesting.sol 27 uint112 public numTokensReservedForVesting = 0; 148 uint112 vestAmt = 0; 353 for (uint256 i = 0; i < length; i++) {
revert()
/require()
strings to save gasCustom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas
File: /contracts/token/FullPremintERC20Token.sol 11 require(supply_ > 0, "NO_ZERO_MINT");
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");
File: /contracts/AccessProtected.sol 25 require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED"); 40 require(admin != address(0), "INVALID_ADDRESS");
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"); // Actually only one of linearvested/cliff amount must be 0, not necessarily both 257 require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); 262 require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp 263 require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); 265 require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); 270 require( 271 ( 272 _cliffReleaseTimestamp > 0 && 273 _cliffAmount > 0 && 274 _cliffReleaseTimestamp <= _startTimestamp 275 ) || ( 276 _cliffReleaseTimestamp == 0 && 277 _cliffAmount == 0 278 ), "INVALID_CLIFF"); 295 require(tokenAddress.balanceOf(address(this)) >= numTokensReservedForVesting + allocatedAmount, "INSUFFICIENT_BALANCE"); 344 require(_startTimestamps.length == length && 345 _endTimestamps.length == length && 346 _cliffReleaseTimestamps.length == length && 347 _releaseIntervalsSecs.length == length && 348 _linearVestAmounts.length == length && 349 _cliffAmounts.length == length, 350 "ARRAY_LENGTH_MISMATCH" 351 ); 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"); // tokenAddress address is already sure to be nonzero due to constructor 449 require(bal > 0, "INSUFFICIENT_BALANCE");
++i
costs less gas than i++
, especially when it's used in for
-loops (--i
/i--
too)Saves 5 gas per loop
File: /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
-loopsThe 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
File: /contracts/VTVLVesting.sol 353 for (uint256 i = 0; i < length; i++) {
unchecked {}
for subtractions where the operands cannot underflow because of a previous require()
or if
-statementrequire(a <= b); x = b - a
=> require(a <= b); unchecked { x = b - a }
File: /contracts/token/VariableSupplyERC20Token.sol 43 mintableSupply -= amount;
File: /contracts/VTVLVesting.sol 104 uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start 107 uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; // length of the interval 167 uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp; // How long since the start 170 uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp; // length of the interval 429 uint112 amountRemaining = finalVestAmt - _claim.amountWithdrawn;
address(0)
In the mint of OpenZeppelin ERC20 implementation, it's checked if the account it is not address(0)
File: /contracts/token/VariableSupplyERC20Token.sol 37 require(account != address(0), "INVALID_ADDRESS");
_msgSender()
when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumptionAlso can remove the Context.sol
of the heredity and his import
File: /contracts/token/FullPremintERC20Token.sol 12 _mint(_msgSender(), supply_);
File: /contracts/token/VariableSupplyERC20Token.sol 32 mint(_msgSender(), initialSupply_);
File: /contracts/AccessProtected.sol 17 _admins[_msgSender()] = true; 18 emit AdminAccessSet(_msgSender(), true); 25 require(_admins[_msgSender()], "ADMIN_ACCESS_REQUIRED");
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);
public
functions to external
functionsThe following functions could be set external to save gas and improve code quality
external
call cost is less expensive than of public
functions
File: /contracts/AccessProtected.sol 39 function setAdmin(address admin, bool isEnabled) public onlyAdmin {
File: /contracts/VTVLVesting.sol 398 function withdrawAdmin(uint112 _amountRequested) public onlyAdmin {
calldata
instead of memory
for read-only arguments in external
functions saves gasWhen a function with a memory
array is called externally, the abi.decode()
step has to use a for-loop to copy each index of the calldata
to the memory
index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length
). Using calldata
directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory
arguments, it's still valid for implementation contracs to use calldata
arguments instead.
If the array is passed to an internal
function which passes the array to another internal
function where the array is modified and therefore memory
is used in the external
call, it's still more gass-efficient to use calldata
when the external
function uses modifiers, since the modifiers may prevent the internal
functions from being called. Structs have the same overhead as an array of length one
Note that I've also flagged instances where the function is public
but can be marked as external
since it's not called by the contract, and cases where a constructor is involved
File: /contracts/VTVLVesting.sol 334 address[] memory _recipients, 335 uint40[] memory _startTimestamps, 336 uint40[] memory _endTimestamps, 337 uint40[] memory _cliffReleaseTimestamps,
ClaimCreated
event to track the recipients of the vesting instead of storage in vestingRecipients
arrayThe vestingRecipients
array it's not used onchain, also the vesting recipient is emited by the ClaimCreated
, with this event can track history recipients and his claim amount
Remove the vestingRecipients
and his getters and setter
File: /contracts/VTVLVesting.sol 52 // Track the recipients of the vesting 53 address[] internal vestingRecipients; 220 /** 221 @notice Return all the addresses that have vesting schedules attached. 222 */ 223 function allVestingRecipients() external view returns (address[] memory) { 224 return vestingRecipients; 225 } 227 /** 228 @notice Get the total number of vesting recipients. 229 */ 220 function numVestingRecipients() external view returns (uint256) { 231 return vestingRecipients.length; 232 } 302 vestingRecipients.push(_recipient); // add the vesting recipient to the list
The ClaimRevoked
event use uint256
type to revocationTimestamp
parameter
File: /contracts/VTVLVesting.sol 436 emit ClaimRevoked(_recipient, amountRemaining, uint40(block.timestamp), _claim);
File: /contracts/VTVLVesting.sol 426 require( _claim.amountWithdrawn < finalVestAmt, "NO_UNVESTED_AMOUNT"); Checked in: 187 return (vestAmt > _claim.amountWithdrawn) ? vestAmt : _claim.amountWithdrawn;
In revokeClaim
can direct calculate the finalVestAmt
like in L292
And finalVestedAmount
function could be marked as external
instead of public
File: /contracts/VTVLVesting.sol From: 422 uint112 finalVestAmt = finalVestedAmount(_recipient); To: 422 uint112 finalVestAmt = _claim.cliffAmount + _claim.linearVestAmount;`