Platform: Code4rena
Start Date: 20/09/2022
Pot Size: $100,000 USDC
Total HM: 4
Participants: 109
Period: 7 days
Judge: GalloDaSballo
Id: 163
League: ETH
Rank: 30/109
Findings: 3
Award: $594.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: zzykxx
Also found by: 8olidity, IllIllI, Lambda, berndartmueller, bytehat, devtooligan, hansfriese, imare, obront, philogy, shung, tonisives, zdhu, zkhorse
https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L562 https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L521
A faulty randomness provider can halt gobbler reveal process indefinitely. A non-revealed gobbler cannot emit any goo, hence that would break the whole game.
Accessing random data on-chain requires a non-atomic transaction. The first half of the transaction is made through an unrestricted external function requestRandomSeed()
. After this function is called, the ArtGobblers.sol
trusts an off-chain entity (i.e.: randomness provider) to execute the second half of the transaction by expecting a call to its acceptRandomSeed()
function. The gobbler reveal process breaks until the randomness provider makes this call, because requestRandomSeed()
sets gobblerRevealsData.waitingForSeed = true
, and only acceptRandomSeed()
sets the value back to false. This boolean value prevents the execution of revealGobblers()
and upgradeRandProvider()
function when it is set to false, breaking the game.
To give a real life example, the off-chain entity can get DOS or go offline indefinitely. During that time anyone can successfully call requestRandomSeed()
before owners manage to upgrade the randomness provider. When this happens, Art Gobblers would be at the mercy of the randomness provider to become operational again (if it ever does). Although ChainLink is not Solana, it is off chain, hence it must be considered unsafe to use without any back up.
Manual review.
You can add a restricted external function to safely reset gobblerRevealsData
if waitingForSeed
is stuck at true
, which would then re-enable upgrading the randomness provider. Example:
function resetRandInfo() external onlyOwner { // Only allow resetting if waitingForSeed is stuck in true. if (!gobblerRevealsData.waitingForSeed || block.timestamp < gobblerRevealsData.nextRevealTimestamp) revert(); delete gobblerRevealsData.randomSeed; delete gobblerRevealsData.waitingForSeed; delete gobblerRevealsData.toBeRevealed; }
#0 - Shungy
2022-09-28T13:41:41Z
Thumbs up because my own submission.
#1 - GalloDaSballo
2022-09-29T21:30:18Z
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0x52, 0x5rings, 0xNazgul, 0xRobocop, 0xSmartContract, 0xdeadbeef, 0xsanson, 8olidity, Amithuddar, Aymen0909, B2, B353N, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, ElKu, Funen, JC, JohnnyTime, Kresh, Lambda, Noah3o6, RaymondFam, ReyAdmirado, RockingMiles, Rolezn, Sm4rty, SuldaanBeegsi, Tadashi, TomJ, Tomio, V_B, Waze, __141345__, a12jmx, ak1, arcoun, asutorufos, aviggiano, berndartmueller, bharg4v, bin2chen, brgltd, bulej93, c3phas, catchup, cccz, ch0bu, cryptonue, cryptphi, csanuragjain, delfin454000, devtooligan, djxploit, durianSausage, eighty, erictee, exd0tpy, fatherOfBlocks, giovannidisiena, hansfriese, ignacio, joestakey, ladboy233, lukris02, m9800, malinariy, martin, minhtrng, obront, oyc_109, pedr02b2, pedroais, pfapostol, philogy, prasantgupta52, rbserver, ronnyx2017, rotcivegaf, rvierdiiev, sach1r0, shung, simon135, throttle, tnevler, tonisives, wagmi, yixxas, zkhorse, zzykxx, zzzitron
55.1985 USDC - $55.20
It is possible to set the mintStart
to zero. Such a mistake would result in much lower price for Pages NFTs. Consider at least ensuring that the mintStart
is greater or equal to block time.
The current one-step ownership transfer method is prone to irreversible user errors. The owner can by mistake send the ownership of the contract to a wrong address, losing all the funds in the contract (in this case, the gobblers in the reserve contract). Consider having a pull method for a second step in ownership transfer where the new owner has to confirm to acquire the ownership.
https://github.com/transmissions11/VRGDAs/blob/f4dec0611641e344339b2c78e5f733bba3b532e0/src/LinearVRGDA.sol#L30 https://github.com/transmissions11/VRGDAs/blob/f4dec0611641e344339b2c78e5f733bba3b532e0/src/LogisticVRGDA.sol#L44-L49
LinearVRGDA.perTimeUnit
, LogisticVRGDA.timeScale
, and LogisticVRGDA.logisticlimit
can all be initialized with negative values. Allthough likelyhood of such a mistake is low, if a deployer makes such a mistake, the VRGDA will work in reverse (i.e.: decreasing price as more sold, allowing all the supply to be gobbled up (pun intended) by quick users), which would be catastrophic. Consider checking these values to be positive in the constructors.
Consider using checked math in getVRGDAPrice()
function. Although you have noted that in practical use overflow would not happen, there could be issues due to dev or user mistakes originating from inheriting contracts. As this is an abstract contract, it should have as few assumptions as possible.
Compounding goo rewards frequently would provide users a higher APY. However, the code does not have a function for updating goo balance just for the purpose of compounding. It instead has addGoo
and removeGoo
functions which can be used for the same effect, but they are inefficient because they make external calls. Having a dedicated compounding function can improve user experience and reduce fees. Example:
function compoundGoo() external { updateUserGooBalance(msg.sender, 0, GooBalanceUpdateType.INCREASE); }
GobblerReserve.sol
is not an abstract contract. Therefore to have consistent bytecode among deployments it would be better to have a fixed pragma. Consider using pragma solidity 0.8.13;
like the rest of the contract.
The documents state that 10 pages per day is targeted to be minted after the logistic to linear VRGDA switch. However, the value is hardcoded as 9 pages per day in the contract. Consider updating the documentation or fixing the contract.
https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L454-L455 https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L660-L661 https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L903-L904 https://github.com/code-423n4/2022-09-artgobblers/blob/d2087c5a8a6a4f1b9784520e7fe75afa3a9cbdbe/src/ArtGobblers.sol#L910-L911
In mintLegendaryGobbler()
and transferFrom()
functions of ArtGobblers.sol
, the goo balance (i.e.: lastBalance
and lastTimestamp
properties of user data) is updated without the emission of associated GooBalanceUpdated()
event. For consistency, consider emitting this event any time goo balance is updated, not just when adding or removing goo.
#0 - Shungy
2022-10-03T17:37:14Z
I1 is invalid as the formula is autocompounding.
#1 - GalloDaSballo
2022-10-06T19:35:19Z
L
NC
Disagree as I believe the Library is stable with negative values, we'd need a specific example to prove how this influences the in-scope system and perhaps would raise to a higher severity
R
R, neat idea
NC
R
I think Informational because the event is never emitted NC
1L 3R 3NC
Pretty good
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xSmartContract, Atarpara, CertoraInc, Deathstore, Deivitto, ElKu, MiloTruck, ReyAdmirado, SnowMan, Tadashi, V_B, __141345__, aviggiano, catchup, djxploit, gogo, pfapostol, philogy, shung
68.6605 USDC - $68.66
Currently whether someone has claimed their mintlist is stored in an address to bool mapping. This is inefficient as it requires zero to non-zero SSTORE any time someone claims their airdrop. It is instead possible to store 256 addresses' mintlist state in a single storage space by using bitmap. With that, most claims would do a non-zero to non-zero SSTORE, significantly reducing costs. The efficiency of this method depends on the ratio of MINTLIST_SUPPLY / NUMBER_OF_MINTLISTED_ADDRESSES
. Closer this value to 1, more efficient it will be. If you are going to have few thousand addresses in the mintlist, I recommend this method. But if you are going to have hundreds of thousands of addresses, then it might not worth the trouble. You can refer to Uniswap's implementation for further info: https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol
In legendaryGobblerPrice()
function numMintedFromGoo
is cached to memory as mintedFromGoo
. However, later in the function, numMintedFromGoo
is read instead of the local equivalent. Consider replacing that instance with mintedFromGoo
to avoid an expensive SLOAD.
#0 - GalloDaSballo
2022-10-04T22:19:23Z
Will give 200 gas for the first report, a coded POC would have prob netted 1k+
G2: Storage variable instead of the cached version is used 100
300
#1 - GalloDaSballo
2022-10-04T22:19:49Z
Uniqueness is good, but need more volume or go all in on the first finding and prove you can save Xk gas
#2 - GalloDaSballo
2022-10-16T16:56:36Z
Saving this one for the uniqueness of the first report vs other more generic 300 gas savings reports