Platform: Code4rena
Start Date: 22/05/2024
Pot Size: $20,000 USDC
Total HM: 6
Participants: 126
Period: 5 days
Judge: 0xsomeone
Total Solo HM: 1
Id: 379
League: ETH
Rank: 15/126
Findings: 3
Award: $28.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Circolors
Also found by: 0rpse, 0x175, 0xAadi, 0xHash, 0xMax1mus, 0xMosh, 0xblack_bird, 0xdice91, 0xfox, 0xhacksmithh, 0xloscar01, 0xrex, 4rdiii, Audinarey, AvantGard, Bigsam, DPS, Dots, Drynooo, Dudex_2004, Evo, Kaysoft, King_, Limbooo, MrPotatoMagic, PENGUN, Sabit, SovaSlava, SpicyMeatball, TheFabled, Utsav, Varun_05, Walter, adam-idarrha, araj, aslanbek, ayden, bctester, biakia, bigtone, brgltd, carrotsmuggler, cats, crypticdefense, dd0x7e8, dhank, fandonov, fyamf, grearlake, iamandreiski, ilchovski, jasonxiale, joaovwfreire, lanrebayode77, m4ttm, merlinboii, niser93, nnez, octeezy, oxchsyston, pamprikrumplikas, rouhsamad, tedox, trachev, turvy_fuzz, twcctop, yotov721, zhaojohnson
0.0056 USDC - $0.01
A user can block another user's function call unlock() by calling the lockOnBehalf() even with a zero value, thereby prolonging the locking time.
There is 2 problems: user could not specify approved list of addresses, which allow call lockOnBehalf() and specify user's address. And second is that there is no minimum required amount of tokens/eth for lock. Attacker could frontrun user's call to unlock(), and just call function lockOnBehalf() and specify user's address and token.
Manual review
Add approve mechanism, where user could specify whitelisted address, which could lock tokens on behalf the user. And add check if _quantity > minQuantity.
DoS
#0 - c4-judge
2024-06-05T12:59:05Z
alex-ppg marked the issue as satisfactory
🌟 Selected for report: SpicyMeatball
Also found by: 0rpse, 0xMosh, 0xblack_bird, 0xdice91, 0xhacksmithh, 0xleadwizard, 0xmystery, Audinarey, AvantGard, Bigsam, Dots, EPSec, Eeyore, Janio, Limbooo, LinKenji, Mahmud, MrPotatoMagic, Myd, Oxsadeeq, Sabit, SovaSlava, Stefanov, Tychai0s, Utsav, Varun_05, Walter, adam-idarrha, ahmedaghadi, araj, aslanbek, ayden, bigtone, c0pp3rscr3w3r, carrotsmuggler, crypticdefense, dhank, fyamf, gajiknownnothing, gavfu, itsabinashb, jasonxiale, joaovwfreire, ke1caM, leegh, merlinboii, mitko1111, n4nika, pfapostol, prapandey031, rouhsamad, sandy, snakeeaterr, stakog, steadyman, swizz, tedox, th3l1ghtd3m0n, trachev, turvy_fuzz, xyz, yashgoel72, zhaojohnson
0.0105 USDC - $0.01
Due to incorrect verification of time duration, the user can reduce the locking time.
The unlockTime variable in setLockDuration() is updated as lastLockTime plus _duration, but _duration must be added to the current time, as in the check above. Example: User have: lastLockTime=100 and unlockTime = 1000. Block.timestamp is 800. User call setLockDuration(300)
if ( uint32(block.timestamp) + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime // 800+300 > 1000. The verification will be passed <--- ) { revert LockDurationReducedError(); }
And new unlockTime will be updated as lastLockTime(100) + duration(300) = 100 + 300 = 400.
uint32 lastLockTime = lockedTokens[msg.sender][tokenContract] .lastLockTime; lockedTokens[msg.sender][tokenContract].unlockTime = lastLockTime + uint32(_duration);
Previous value of unlockTime was 1000, now - 400.
Manual review
- uint32 lastLockTime = lockedTokens[msg.sender][tokenContract].lastLockTime; lockedTokens[msg.sender][tokenContract].unlockTime = - lastLockTime + + block.timestamp + uint32(_duration);
Invalid Validation
#0 - c4-judge
2024-06-04T12:40:52Z
alex-ppg marked the issue as duplicate of #89
#1 - c4-judge
2024-06-05T12:53:50Z
alex-ppg marked the issue as partial-75
#2 - c4-judge
2024-06-05T12:54:34Z
alex-ppg changed the severity to 3 (High Risk)
28.7985 USDC - $28.80
When user lock tokens, and execute calculation count of nfts, some value could be write into remainder value. But after unlockTime, user can call unlock() and get all tokens back, but remainder value does not delete, so in next call to lock() function, he could use less tokens, than it needed, because will be uses specified quantity and remainder value from previous lock.
Example: configuredToken.nftCost = 3; User call lock(10) -> quantity=10, remainder=1 (10%3=1)
function lock() { .... remainder = quantity % configuredToken.nftCost; numberNFTs = (quantity - remainder) / configuredToken.nftCost; ... lockedToken.remainder = remainder; // 1 lockedToken.quantity += _quantity; // 3
When block.timestamp will be > unlockTime, user call unlock(10).
function unlock() { ... lockedToken.quantity -= _quantity; // But dont set 0 in remainder value.
User call lock(8)
function lock() { ... uint256 quantity = _quantity + lockedToken.remainder; // 8 + 1= 9 ... remainder = quantity % configuredToken.nftCost; // 9%3=0 numberNFTs = (quantity - remainder) / configuredToken.nftCost; // (9-0) / 3=3
So, user use less tokens in second call of lock, than in first call.
Manual review
function unlock() { ... lockedToken.quantity -= _quantity; + lockedToken.remainder = 0;
Other
#0 - c4-judge
2024-06-05T13:04:18Z
alex-ppg marked the issue as satisfactory