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: 8/198
Findings: 4
Award: $805.45
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: TomJ
Also found by: ayeslick, csanuragjain, pashov
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L245
As per the comment in _createClaimUnchecked function, product team wanted _startTimestamp to be from past as well. But there is no limit on how much it could be in the past which means it could be last 100 years as well. In case it is too much in past, it would be possible to make claim for about 99% of amount at just starting of contract with no way for Admin to rectify the mistake (if user claims fast)
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); }
function _createClaimUnchecked( address _recipient, uint40 _startTimestamp, uint40 _endTimestamp, uint40 _cliffReleaseTimestamp, uint40 _releaseIntervalSecs, uint112 _linearVestAmount, uint112 _cliffAmount ) private hasNoClaim(_recipient) { ... require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); Claim memory _claim = Claim({ startTimestamp: _startTimestamp, endTimestamp: _endTimestamp, cliffReleaseTimestamp: _cliffReleaseTimestamp, releaseIntervalSecs: _releaseIntervalSecs, cliffAmount: _cliffAmount, linearVestAmount: _linearVestAmount, amountWithdrawn: 0, isActive: true }); ... }
As we can see here the _startTimestamp we provided simply pass all test case and a new claim gets created
Since _startTimestamp is 100 years in past from current date and _endTimestamp is block.timestamp+1month, so once this claim is made claim recipient would be able to make a claim of 99% already since as per contract 100 years have already passed on a vested duration of 100 years 1month
_currentTimestamp-_startTimestamp = block.timestamp-block.timestamp+100 years = 100 years
Make a limit on how back _startTimestamp could be. Like allowing claim to be maximum 1 year past current time
#0 - 0xean
2022-09-25T19:03:56Z
dupe of #292
🌟 Selected for report: Czar102
Also found by: 0xSmartContract, Lambda, Respx, csanuragjain, hansfriese, sashik_eth
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L407
Admin can withdraw locked funds
function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { // Allow the owner to withdraw any balance not currently tied up in contracts. uint256 amountRemaining = tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting; require(amountRemaining >= _amountRequested, "INSUFFICIENT_BALANCE"); // Actually withdraw the tokens // Reentrancy note - this operation doesn't touch any of the internal vars, simply transfers // Also following Checks-effects-interactions pattern tokenAddress.safeTransfer(_msgSender(), _amountRequested); // Let the withdrawal known to everyone emit AdminWithdrawn(_msgSender(), _amountRequested); }
tokenAddress.balanceOf(address(this)) = 400 numTokensReservedForVesting = 300 amountRemaining = 400-300 = 100
So Admin can withdraw 100 amount securing the 300 amount of User A
Below code tries to transfer 100 amount to Admin contract
tokenAddress.safeTransfer(_msgSender(), _amountRequested);
Assume tokenAddress is a custom ERC20 contract which makes one event call to recipient contract before transferring any amount.
Once call is made to Admin contract, it reenters the withdrawAdmin function again. (Remember transfer has not completed yet). Since no state variables has changed so while reentering the state remains same
tokenAddress.balanceOf(address(this)) = 400 numTokensReservedForVesting = 300 amountRemaining = 400-300 = 100
tokenAddress.safeTransfer(_msgSender(), _amountRequested);
Mark the withdrawAdmin function with nonReentrant modifier (openzepplin ReentrancyGuard)
#0 - 0xean
2022-09-24T22:23:09Z
dupe of #6
🌟 Selected for report: AkshaySrivastav
Also found by: 0v3rf10w, 0x040, 0x1f8b, 0x4non, 0x5rings, 0x85102, 0xA5DF, 0xDecorativePineapple, 0xNazgul, 0xSky, 0xSmartContract, 0xbepresent, 0xf15ers, 0xmatt, 2997ms, Aeros, Aymen0909, B2, Bahurum, Bnke0x0, CertoraInc, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diana, Diraco, Dravee, ElKu, Funen, IllIllI, JC, JLevick, JohnSmith, JohnnyTime, KIntern_NA, Lambda, Margaret, MasterCookie, OptimismSec, RaymondFam, Respx, ReyAdmirado, RockingMiles, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, SooYa, StevenL, TomJ, Tomo, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, ak1, async, ayeslick, aysha, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, c3phas, carrotsmuggler, cccz, ch13fd357r0y3r, chatch, cryptostellar5, cryptphi, csanuragjain, d3e4, datapunk, delfin454000, dic0de, djxploit, durianSausage, eighty, erictee, exd0tpy, fatherOfBlocks, gogo, got_targ, hansfriese, ignacio, ikbkln, indijanc, innertia, joestakey, karanctf, ladboy233, leosathya, lukris02, martin, medikko, millersplanet, nalus, natzuu, neko_nyaa, neumo, obront, oyc_109, pcarranzav, peanuts, pedr02b2, pedroais, peiw, peritoflores, prasantgupta52, rajatbeladiya, rbserver, reassor, ret2basic, rokinot, romand, rotcivegaf, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, sorrynotsorry, supernova, tibthecat, tnevler, ubermensch, yongskiws, zzykxx, zzzitron
18.8574 USDC - $18.86
Issue: In the constructor of VariableSupplyERC20Token.sol, it is always possible to have both initialSupply_ and maxSupply_ equal to zero (Admin allows unlimited minting with initial 0 tokens). However this is not possible due to below check
require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");
Recommendation: Remove the below check
require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");
🌟 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.086 USDC - $9.09
Contract: https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L107
Issue: In hasActiveClaim function, there is no need to check require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); since it is more than enough to check _claim.isActive==true in hasActiveClaim function
Recommendation: Modify the hasActiveClaim function to eliminate the below require condition
require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM");