Platform: Code4rena
Start Date: 07/04/2022
Pot Size: $100,000 USDC
Total HM: 20
Participants: 62
Period: 7 days
Judge: LSDan
Total Solo HM: 11
Id: 107
League: ETH
Rank: 18/62
Findings: 3
Award: $720.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hickuphh3
Also found by: 0x1f8b, AuditsAreUS, Foundation, Kthere, Meta0xNull, WatchPug, rayn
https://github.com/code-423n4/2022-04-jpegd/blob/main/contracts/vaults/NFTVault.sol#L375 https://github.com/code-423n4/2022-04-jpegd/blob/main/contracts/lock/JPEGLock.sol#L59
If a user calls NFTVault.finalizePendingNFTValueETH
a second time without first calling JPEGLock.unlock
to recover their previous lockup, their balance will be overwritten leaving the previous lockup balance unrecoverable.
POC by adding the following to NFTValue.ts
line 640:
// Repeat the previous few lines to set up another value change await nftVault .connect(dao) .setPendingNFTValueETH(index, units(50000)); await jpeg.transfer(user.address, units(12000000)); await jpeg.connect(user).approve(locker.address, units(12000000)); await nftVault.connect(user).finalizePendingNFTValueETH(index); // Locker is holding both deposits expect(await jpeg.balanceOf(locker.address)).to.equal(units(12000000 * 2)); // But only one is attributed to a user expect((await locker.positions(index)).lockAmount).to.eq(units(12000000))
Manual review and modifying existing tests.
JPEGLock
's lockFor
could require positions[_nftIndex].lockAmount == 0
to avoid this issue. If the scenario is desirable (allowing the same user to finalize a value change for the same NFT) then maybe you could find a reasonable way to merge them or otherwise account for the original value locked; such as only calling jpeg.safeTransferFrom
for the delta amount (possibly refunding a surplus).
#0 - spaghettieth
2022-04-13T19:00:08Z
Duplicate of #10
🌟 Selected for report: Dravee
Also found by: 0x1f8b, 0xDjango, 0xkatana, AuditsAreUS, Cityscape, Foundation, Funen, Hawkeye, IllIllI, JC, JMukesh, Jujic, Kthere, PPrieditis, Picodes, Ruhum, TerrierLover, TrungOre, WatchPug, berndartmueller, catchup, cccz, cmichel, delfin454000, dy, ellahi, hickuphh3, horsefacts, hubble, hyh, ilan, jayjonah8, kebabsec, kenta, minhquanym, pauliax, rayn, reassor, rfa, robee, samruna
152.5804 USDC - $152.58
FlashEscrow: call_failed
.newEpoch
, add
, and set
should emit events. This would make it easier for frontends integrating (or subgraphs) to monitor and message significant changes like these.🌟 Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xkatana, Cityscape, Cr4ckM3, FSchmoede, Foundation, Funen, Hawkeye, IllIllI, JMukesh, Meta0xNull, PPrieditis, Picodes, TerrierLover, Tomio, WatchPug, berndartmueller, catchup, delfin454000, dirk_y, ellahi, hickuphh3, ilan, kebabsec, kenta, nahnah, rayn, rfa, robee, rokinot, saian, securerodd, slywaters, sorrynotsorry
96.1225 USDC - $96.12
owner
+ unlockAt
saving ~21k gas. Could use up to 96 bits and still pack into 1 slot, 96 bits is way more than you need for a date.immutable
variables to save gas. Using a constructor and switching to IERC20Upgradeable public immutable jpeg
saves ~2.1k per stake/unstake call since there is no SLOAD required for immutable variables (they have the gas efficiency of a constant, except for the constructor cost itself).nftAddress
could be made immutable (similar to the point above), saving ~2.1k per call.precompute
to avoid unnecessary calculations anytime _executeTransfer
is called. _amount <= balanceOf(msg.sender)
is redundant, the internal _burn
call will validate that as well. Saving ~500 gas._massUpdatePools
caches but claimAll
does not.