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: 137/198
Findings: 2
Award: $19.60
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol#L13-L34 https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol#L36-L50
As the supplied VariableSupplyERC20Token does not support burning a supply limit can be set on construction via the maxSupply_ parameter. This is copied to a public mintableSupply variable. To enforce this supply limit the mint function has a require check ensuring the amount to be minted does not exceed the mintableSupply value. However, the require test is contained inside an if statement that is only executed if mintableSupply > 0. This means any token using this contract will have an infinite supply once mintableSupply drops to 0, even when otherwise expected.
This issue may be triggered accidentally and may significantly impact token price for VTVL-based project users. Therefore I have reported this issue as High. A project depending on a fixed supply may encounter significant downside when it becomes apparent supply isn't really limited.
VariableSupplyERC20Token.sol's constructor allows the token creator to specify an initial and max supply. The maximum token supply value is stored in maxSupply_. Following a check to ensure either initialSupply or maxSupply is greater than zero, the value of maxSupply_ is assigned to the public variable mintableSupply. The initial supply is then minted by calling the mint() function.
The mint() function checks that the destination address is not 0, and that mintableSupply > 0. When mintableSupply > 0 a require statement checks that the amount to be minted is less than the mintable Supply. The mintableSupply value is then reduced by the amount to be minted. The if statement then exits.
If mintableSupply is 0 this means mint at will. The problem is that the mintableSupply value will eventually = 0 as new tokens are minted. Once this happens the if check will never apply, and tokens will simply be minted at will.
VSCode, Remix
The mintableSupply variable alone is insufficient to track all possible states under which minting should take place. Two new variables should be used to track state, set in the contract:
uint256 public maxSupply; // The total max supply. Variable on unlimitedMint, otherwise fixed. bool public unlimitedMint; // Determine if unlimitedMint is enabled.
These two allow the contract to correctly enumerate all possible conditions under which a mint is possible:
This also removes the need to check that amount <= mintableSupply, as a greater value would also exceed case 2 above.
To avoid ABI changes (as this may require front-end changes), the following could be added to the constructor:
maxSupply = maxSupply_; if (maxSupply == 0){ unlimitedMint = true; }
The code above enables an unlimitedMint mode if maxSupply == 0 as mentioned in the comments. Two capture cases 1 and 2 above, code such as the following could be used:
function mint(address account, uint256 amount) public onlyAdmin { uint256 total = totalSupply() + amount; require(account != address(0), "INVALID_ADDRESS"); // If we're using maxSupply, we need to make sure we respect it // unless unlimitedMint is enabled. // mintableSupply = 0 means mint at will require(total <= maxSupply || unlimitedMint, "INVALID_AMOUNT"); if(mintableSupply > 0) { // We need to reduce the amount only if we're using the limit, if not just leave it be mintableSupply -= amount; } if(unlimitedMint) { // Increase maxSupply to reflect the change in total Supply. maxSupply += amount; } _mint(account, amount); }
None Found.
#0 - 0xean
2022-09-23T23:52:32Z
dupe of #3
🌟 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
The finalVestedAmount() function in VTVLVesting.sol asserts to return the "total vested at the end of the schedule". It does this by using a _claim.endTimestamp value to calculate a claim via the _baseVestedAmount() function.
When a user partially withdraws during the vesting period, the claim's amountWithdrawn values are updated. This change is not reflected in the finalVestedAmount() function, which will report the original final value. The ambiguity comes from interpretations of "total" in the comments versus "final" in the function name.
It may be helpful to rename the function to totalVestedAmount, if this is supposed to reflect the sum total vested throughout the schedule's lifetime. If this should reflect the final amount vested at the end of the schedule, replace line 208 with:
return _baseVestedAmount(_claim, _claim.endTimestamp) - _claim.amountWithdrawn;
There is a possibility that people may be led to believe that an amount remaining vested in a VTVL-produced vesting contract is significantly higher than in reality. The impact of this issue is contextual, therefore I have rated this as Low/QA.
The constructor in VTVLVesting.sol checks that the supplied _tokenAddress value is not 0. It doesn't check that it's a valid contract.
Check the supplied address' extcodesize to determine whether or not this is a valid contract.
uint size; assembly { size := extcodesize(addr) } require(size != 0, "NOT_CONTRACT");