Platform: Code4rena
Start Date: 06/12/2022
Pot Size: $36,500 USDC
Total HM: 16
Participants: 119
Period: 3 days
Judge: berndartmueller
Total Solo HM: 2
Id: 189
League: ETH
Rank: 17/119
Findings: 4
Award: $349.29
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: HollaDieWaldfee
Also found by: 0xNazgul, cccz, hansfriese, obront
289.163 USDC - $289.16
The documentation says that authors can specify metadata and royalties for NFTs with specific IDs via setTokenURI()/setTokenRoyalty()
1. Set up the token URI by calling setTokenURI with the token ID and the URI (arweave recommended) 2. If the artist would like sales and royalties to go somewhere other than the default royalty receiver, they must call setTokenRoyalty with the following variables: ...
Authors can only call setDefaultRoyalty to set the default royalty, and setBaseURI to set the baseURI. This is inconsistent with the documentation, thus limiting authors to setting different metadata and royalties for different NFTs
https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/Escher721.sol#L64-L69 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/uris/Base.sol#L10-L12
None
Consider implementing setTokenURI()/setTokenRoyalty() to allow authors to specify metadata and royalties for NFTs with different IDs
#0 - c4-judge
2022-12-14T09:13:20Z
berndartmueller marked the issue as duplicate of #181
#1 - c4-judge
2023-01-03T15:50:54Z
berndartmueller marked the issue as satisfactory
#2 - liveactionllama
2023-01-11T20:13:06Z
Duplicate of #68
🌟 Selected for report: RaymondFam
Also found by: 0xdeadbeef0x, 0xhacksmithh, AkshaySrivastav, Awesome, Bnke0x0, CRYP70, HollaDieWaldfee, JC, Parth, Rahoz, Tutturu, __141345__, ahmedov, ajtra, asgeir, aviggiano, bin2chen, btk, carrotsmuggler, cccz, chaduke, cryptonue, dic0de, fatherOfBlocks, fs0c, hansfriese, jonatascm, karanctf, ladboy233, lumoswiz, martin, obront, pashov, pauliax, rvierdiiev, shark, simon135, supernova, tourist, yellowBirdy, zapaz, zaskoh
0.6136 USDC - $0.61
FixedPrice._end/LPDA.buy/LPDA.refund/OpenEdition.finalize use payable.transfer to send eth to the user. This is unsafe as transfer has hard coded gas budget and can fail when the user is a smart contract. Whenever the user either fails to implement the payable fallback function or cumulative gas cost of the function sequence invoked on a native token transfer exceeds 2300 gas consumption limit the native tokens sent end up undelivered and the corresponding user funds return functionality will fail each time.
https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/FixedPrice.sol#L109-L110 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/LPDA.sol#L85-L86 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/LPDA.sol#L105-L106 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/OpenEdition.sol#L92-L93
None
Using low-level call.value(amount) with the corresponding result check or using the OpenZeppelin Address.sendValue is advised: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L60
#0 - c4-judge
2022-12-10T00:30:15Z
berndartmueller marked the issue as duplicate of #99
#1 - c4-judge
2023-01-03T12:47:56Z
berndartmueller marked the issue as satisfactory
🌟 Selected for report: adriro
Also found by: 0xA5DF, 0xRobocop, AkshaySrivastav, Ch_301, HollaDieWaldfee, KingNFT, bin2chen, carrotsmuggler, cccz, gasperpre, hansfriese, hihen, jonatascm, lumoswiz, neumo
28.357 USDC - $28.36
When the author creates a sale of Escher721, the id of the NFT bought by the user is incremented.
uint48 newId = uint48(_amount) + sale_.currentId; require(newId <= sale_.finalId, "TOO MANY"); for (uint48 x = sale_.currentId + 1; x <= newId; x++) { nft.mint(msg.sender, x); }
And in Escher721 contract, there may be more than one minter, if other minter mint the NFT with id in the sale range, the sale will fail when minting the NFT with that id in the buy function, and because Escher721 does not support burn NFT, it cannot be recovered, thus the ETH in the Fixed Price and LPDA sales is locked in the contract.
uint48 newId = uint48(_amount) + sale_.currentId; require(newId <= sale_.finalId, "TOO MANY"); for (uint48 x = sale_.currentId + 1; x <= newId; x++) { nft.mint(msg.sender, x); }
Due to the high impact and low likelihood, I consider the severity to be medium
Consider the following scenarios.
An author creates a FixedPrice sale for NFTs with IDs 1 to 100 in Escher721 at 1 ETH A minter in Escher721 minted an NFT with ID 100. When the user buys the NFT in the FixedPrice sale, the sale cannot be ended because the NFT with ID 100 has already been minted, resulting in the ETH sent by the user being locked in the contract.
https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/Escher721.sol#L51-L53 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/FixedPrice.sol#L62-L67 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/LPDA.sol#L73-L88
None
Consider allowing only one minter to exist for Escher721, transferring the minter to the sale contract when the author creates the sale, and then to the author when the sale ends
#0 - c4-judge
2022-12-13T11:36:34Z
berndartmueller marked the issue as duplicate of #390
#1 - c4-judge
2023-01-02T20:05:35Z
berndartmueller marked the issue as satisfactory
🌟 Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0x4non, 0xA5DF, 0xNazgul, 0xRobocop, 0xfuje, Bnke0x0, HollaDieWaldfee, Lambda, RaymondFam, TomJ, _Adam, adriro, ajtra, carrotsmuggler, cccz, danyams, gasperpre, hansfriese, helios, immeas, joestakey, ladboy233, nameruse, obront, oyc_109, rvierdiiev, saian, sakshamguruji, seyni, slvDev, yixxas, zaskoh
31.1555 USDC - $31.16
Generative.setSeedBase uses block.timestamp/block.number/blockhash to generate random numbers, which results in the generated random numbers being predictable, and since the generated random numbers are generally used to determine the properties of NFTs, users can predict the random numbers and thus purchase rarer NFTs.
Consider using chainlink VRF for random number generation https://docs.chain.link/vrf/v2/introduction/
_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver.
https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/FixedPrice.sol#L66-L67 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/LPDA.sol#L74-L75 https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/OpenEdition.sol#L67-L68
Use _safeMint instead of _mint
LPDAFactory.createLPDASale should check startPrice >= dropPerSecond * (end-start), if startPrice < dropPerSecond * (end-start), then LPDA.getPrice() may revert due to overflow, and buy()/refund() will fail, making ETH locked in the contract
function getPrice() public view returns (uint256) { Sale memory temp = sale; (uint256 start, uint256 end) = (temp.startTime, temp.endTime); if (block.timestamp < start) return type(uint256).max; if (temp.currentId == temp.finalId) return temp.finalPrice; uint256 timeElapsed = end > block.timestamp ? block.timestamp - start : end - start; return temp.startPrice - (temp.dropPerSecond * timeElapsed); }
Consider checking startPrice >= dropPerSecond * (end-start) in LPDAFactory.createLPDASale
When creating a sale, the currentId is at least 0, but in the buy function, only the NFT with ID currentId + 1 will be minted, which makes it impossible to create a sale for an NFT with ID 0
function buy(uint256 _amount) external payable { Sale memory sale_ = sale; IEscher721 nft = IEscher721(sale_.edition); require(block.timestamp >= sale_.startTime, "TOO SOON"); require(_amount * sale_.price == msg.value, "WRONG PRICE"); uint48 newId = uint48(_amount) + sale_.currentId; require(newId <= sale_.finalId, "TOO MANY"); for (uint48 x = sale_.currentId + 1; x <= newId; x++) { nft.mint(msg.sender, x); }
Consider allowing NFTs with ID 0 to be minted in the buy function
#0 - c4-judge
2023-01-04T10:45:47Z
berndartmueller marked the issue as grade-b