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: 17/198
Findings: 3
Award: $408.52
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Trust
Also found by: 0xSky, CertoraInc, KIntern_NA, bin2chen, hansfriese, neko_nyaa, neumo, rokinot, wastewa
when calculate the vested amount, if linearVestAmount and endTimestamp are large will lead to overflow uint112, the token that has entered the contract will be locked in the contract
Assume that after 1 year to claim,token decimals is 18 Then as long as the user saves: type(uint112).max / 365 days / 10**18 = 164646653 if more than 164646653 tokens can create "Claim" successfully , but withdraw will overflow and cause the token to be locked this number level is still very possible, if the time is longer , that linearVestAmount can be smaller
function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { ... //**** overflow (uint112*uint40) *****// // linearVestAmount > 164646653* 10**18 and truncatedCurrentVestingDurationSecs = 365 days uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; ....
The overflow causes both withdraw() and revokeClaim() to fail, and the token is permanently locked in the contract
Convert to uint256,calculate and then convert back to uint112
function _baseVestedAmount(Claim memory _claim, uint40 _referenceTs) internal pure returns (uint112) { ... --- uint112 linearVestAmount = _claim.linearVestAmount * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; +++ uint256 linearVestAmount = uint256(_claim.linearVestAmount) * truncatedCurrentVestingDurationSecs / finalVestingDurationSecs; // Having calculated the linearVestAmount, simply add it to the vested amount --- vestAmt += linearVestAmount; +++ vestAmt += uint112(linearVestAmount); +++ //cast to uint112 is safe, linearVestAmount will not be greater than type(uint112).max , because finalVestingDurationSecs >= truncatedCurrentVestingDurationSecs
#0 - 0xean
2022-09-24T19:21:48Z
dupe of #95
🌟 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
VariableSupplyERC20Token uses mintableSupply to control the maximum mint, but the error of each mint will reduce this value accordingly, and when mintableSupply reduct to 0, there is no limit again, you can mint at will
1.init mintableSupply = 100 2.mint 100 token 3.after mint, mintableSupply = 0 , you can mint at will
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; //******** reduce,when mintableSupply=0,you can mint at will********// } _mint(account, amount); }
don't reduce mintableSupply , check with totalSupply
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(mintableSupply > 0) { +++ require(totalSupply() <= mintableSupply, "INVALID_AMOUNT"); +++ } }
#0 - 0xean
2022-09-24T00:26:16Z
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
admin can cancel themselves, which may result in no admin and losing control of the contract forever
setAdmin() is used to add/reduce/replace administrators The normal when replacing is :
add check
function setAdmin(address admin, bool isEnabled) public onlyAdmin { require(admin != address(0), "INVALID_ADDRESS"); +++ require((admin!=_msgsender(),"NOT SELF"); +++ require(_admins[admin]!=isEnable,"NOT CHANGE"); _admins[admin] = isEnabled; emit AdminAccessSet(admin, isEnabled); }
#0 - 0xean
2022-09-23T23:37:50Z
dupe of #469
#1 - 0xean
2022-10-09T23:02:41Z
downgraded to QA