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: 46/198
Findings: 3
Award: $136.28
π 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
It has been identified that a user can mint more tokens than the maxSupply_
in the VariableSupplyERC20Token.sol
smart contract. As noted in the comments of the smart contract:
// If we're using maxSupply, we need to make sure we respect it
So, if the constructor is called with 100 as the maxSupply_
value, the maxTokens
that can be minted are 100.
However, the check in the mint()
function does not correctly enforce this condition.
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); }
If the mintableSupply
becomes 0 after the subtraction at L:41 mintableSupply -= amount;
, the mint
operation at L:45 _mint(account, amount);
will continue normally.
Bellow is a forge test noting the above issue.
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/token/VariableSupplyERC20Token.sol"; contract CounterTest is Test { address owner = address(1); VariableSupplyERC20Token variableSupplyERC20Token; function setUp() public { vm.startPrank(owner); //deploy the VariableSupplyERC20Token smart contract with 100 as the max supply variableSupplyERC20Token = new VariableSupplyERC20Token("name1", "symbol1", 0, 100); vm.stopPrank(); } function test1() public{ vm.startPrank(owner); //log the mintableSupply console.log(variableSupplyERC20Token.mintableSupply()); //mint 100 tokens to the user variableSupplyERC20Token.mint(owner, 100); //log the token balance of the user console.log(variableSupplyERC20Token.balanceOf(owner)); //log once againt the mintableSupply in order to make sure that is zero console.log(variableSupplyERC20Token.mintableSupply()); //mint 200 more tokens to the user variableSupplyERC20Token.mint(owner, 200); //log once again the token balance of the user console.log(variableSupplyERC20Token.balanceOf(owner)); //log once againt the mintableSupply in order to make sure that is zero console.log(variableSupplyERC20Token.mintableSupply()); vm.stopPrank(); } }
Manual Code Review
It is recommended to add a require statement in the beginning of the mint()
function to ensure that the mintableSupply
is > 0
or mintAtWill
is true
. The mintAtWill
variable can be set to true if the maxSupply_ == 0
.
#0 - 0xean
2022-09-24T00:14:24Z
dupe of #3
π Selected for report: __141345__
Also found by: 0xDecorativePineapple, CertoraInc, IllIllI, JohnSmith, MiloTruck, djxploit, hyh, rbserver, zzzitron
https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L388 https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L407 https://github.com/code-423n4/2022-09-vtvl/blob/f68b7f3e61dad0d873b5b5a1e8126b839afeab5f/contracts/VTVLVesting.sol#L450
The VTVL documentation clearly notes that: VTVLVesting contract supports any ERC20 token, so any other token can be substituted for this one
.
But, it doesn't appear to support rebasing/deflationary/inflationary
tokens whose balance changes during transfers or over time. The necessary checks include at least verifying the amount of tokens transferred to contracts before and after the actual transfer to infer any fees/interest.
VTVLVesting.sol#L388 VTVLVesting.sol#L407 VTVLVesting.sol#L450
Manual Code Review
#0 - 0xean
2022-09-25T13:58:32Z
dupe of #278
π 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
_baseVestedAmount
function leads to zero amount available for withdraw after some time passesIt has been identified that is possible for a user to not be able to withdraw any funds, even he has an active claim and some time has passed through the linear vesting period.
This happens due to the rounding in the _baseVestedAmount
function.
The available withdrawn amount is 0
when linearVestAmount * truncatedCurrentVestingDurationSecs
< finalVestingDurationSecs
Suppose that an administrator creates claim for userA
with the values:
address _recipient : user A uint40 _startTimestamp : uint40(10) uint40 _endTimestamp: uint40(545443020800) uint40 _cliffReleaseTimestamp: 0 uint40 _releaseIntervalSecs: 10 uint112 _linearVestAmount: uint112(3965) uint112 _cliffAmount: 0
After 9000
seconds the amount that is available to be withdrawn will be 0.
This is inconvenient for the users as they try to withdraw the funds and the transaction reverts.
Bellow is a foundry test noting the issue
pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/test/TestERC20Token.sol"; import "../src/VTVLVesting.sol"; contract CounterTest is Test { address owner = address(1); address user1 = address(2); address user2 = address(3); TestERC20Token testERC20Token; VTVLVesting vtvlVesting; function setUp() public { //start a prank as the owner vm.startPrank(owner); //deploy a test token with 4000*10**18 initial supply testERC20Token = new TestERC20Token("testName", "testSymbol", 4000); //deploy the vtvlVesting contract vtvlVesting = new VTVLVesting(testERC20Token); //transfer 200 * 10 ** 18 tokens to the vtvlVesting smart contract testERC20Token.transfer(address(vtvlVesting), 900 * 10 ** 18); vm.stopPrank(); } function testVerifyZeroWithdraw() public { vm.prank(owner); vtvlVesting.createClaim(user1, uint40(10), uint40(545443020800), 0, 10, uint112(3965), 0); skip(9000); console.log(vtvlVesting.claimableAmount(user1)); vm.startPrank(user1); vtvlVesting.withdraw(); vm.stopPrank(); } }