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: 37/198
Findings: 3
Award: $227.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
218.0935 USDC - $218.09
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L429-L433
revokeClaim
should send the user what is already vested at this moment before deactivating the claim.
e.g. imagine a situation when a user has never claimed the tokens and has 90% already vested but suddenly the admin decides to revoke it and the user is left with nothing. I believe this is not fair from the users' perspective and they should receive already accrued tokens. Even more unlikely but the worse situation would be if admin keys were compromised and a malicious actor was able to revoke all the claims and then withdrawAdmin
all the tokens.
I am submitting this as of Medium severity because it might incur users losing their legitimate tokens that were already claimable but not yet claimed.
The protocol should respect users and regard their rewards. Already accrued tokens should be sent to the users before deactivating the claim.
#0 - 0xean
2022-09-25T14:16:32Z
dupe of #475
🌟 Selected for report: Czar102
Also found by: 0xDecorativePineapple, 0xNazgul, 0xSky, 0xbepresent, 0xmatt, Atarpara, Bahurum, DimitarDimitrov, Franfran, GimelSec, JGcarv, JLevick, Junnon, OptimismSec, Rolezn, Ruhum, Soosh, Tomo, Trust, __141345__, adriro, ajtra, bin2chen, cRat1st0s, cccz, cryptonue, d3e4, innertia, jag, joestakey, neumo, obront, pashov, pauliax, pcarranzav, peanuts, rajatbeladiya, rbserver, reassor, seyni, wagmi, zzykxx, zzzitron
0.7375 USDC - $0.74
When minting the tokens in VariableSupplyERC20Token
the mintableSupply
is reduced, thus you can bypass the max supply limit once it hits 0 because 0 means unlimited. As far as I understand, the total supply should never reach the cap set in the constructor (parameter maxSupply_
).
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); }
Steps:
mintableSupply
.mintableSupply
is reduced to 0.Recommendation: rename mintableSupply
to maxSupply
(0 meaning unlimited). Do not decrement it here and before performing the mint check that maxSupply == 0 || totalSupply() + amount <= maxSupply
.
#0 - 0xean
2022-09-24T00:38:52Z
dupe of #3
🌟 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
_cliffReleaseTimestamp > 0
is not really needed here:require( _cliffReleaseTimestamp > 0 && _cliffAmount > 0 && _cliffReleaseTimestamp <= _startTimestamp )
because _startTimestamp
was validated before:
require(_startTimestamp > 0, "INVALID_START_TIMESTAMP");
uint112 public numTokensReservedForVesting = 0;
vestAmt
:if(_referenceTs >= _claim.cliffReleaseTimestamp) { vestAmt += _claim.cliffAmount; }
can save a bit of gas by replacing +=
with just =
.
unchecked
maths here and everywhere else where overflow/underflow is not possible:if(_referenceTs > _claim.startTimestamp) { uint40 currentVestingDurationSecs = _referenceTs - _claim.startTimestamp;
_baseVestedAmount
is queried:uint40 finalVestingDurationSecs = _claim.endTimestamp - _claim.startTimestamp;
even though nor start, nor end timestamps do not change. Wouldn't it be better to store the duration in the Claim
struct then?
usrClaim.amountWithdrawn
variable:require(allowance > usrClaim.amountWithdrawn, "NOTHING_TO_WITHDRAW"); ... uint112 amountRemaining = allowance - usrClaim.amountWithdrawn;
usrClaim.amountWithdrawn = allowance
:uint112 allowance = vestedAmount(_msgSender(), uint40(block.timestamp)); ... uint112 amountRemaining = allowance - usrClaim.amountWithdrawn; ... usrClaim.amountWithdrawn += amountRemaining;