Platform: Code4rena
Start Date: 24/02/2022
Pot Size: $75,000 USDC
Total HM: 21
Participants: 28
Period: 7 days
Judge: alcueca
Total Solo HM: 15
Id: 94
League: ETH
Rank: 13/28
Findings: 2
Award: $949.97
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: thankthedark
Also found by: Afanasyevich, cmichel
657.3838 USDC - $657.38
Within a NFTMarketPrivateSale contract, buyers are allowed to purchase a seller's NFT. This is done through a seller providing a buyer a EIP-712 signature. The buyer can then call #buyFromPrivateSaleFor
providing the v, r, and s values of the signature as well as any additional details to generate the message hash. If the signature is valid, then the NFT is transferred to the buyer.
The problem with the code is that EIP-712 signatures can be re-used within a small range of time assuming that the original seller takes back ownership of the NFT. This is because the NFTMarketPrivateSale#buyFromPrivateSaleFor method has no checks to determine if the EIP-712 signature has been used before.
Consider the following example:
#buyFromPrivateSaleFor
again with the same parameters they provided in the original private sale purchase in step 1.The #buyFromPrivateSaleFor
function runs several validation checks before transferring the NFT over to the buyer. The validations are as follows:
As you can see, there are no checks that the EIP-712 signature has been used before. If the original NFT seller purchases back the NFT, then they are susceptible to having the original buyer taking back the NFT. This can be problematic if the NFT has risen in value, as the original buyer can utilize the same purchase amount from the first transaction in this malicious transaction.
Pen and paper
Most contracts utilize nonces when generating EIP-712 signatures to ensure that the contract hasn't been used for. When a nonce is injected into a signature, it makes it impossible for re-use, assuming of course the nonce feature is done correctly.
#0 - HardlyDifficult
2022-03-03T12:23:53Z
Yes, this is a good point to raise and something like this could happen because it's not intuitive to think that the private sale remains valid after it's been used.
This is mitigated by the short time window we use for Private Sales. Our frontend uses 24-hour expirations and in the contract we ensure the window is <= 48 hours.
In order to remain backwards compatible with our existing integration and any signatures which are outstanding at the time of the upgrade, we decided not to use nonce
as recommended. Instead we simply store a mapping tracking if the exact private sale terms have already been used.
Here's the primary addition:
// Ensure that the offer can only be accepted once. if (privateSaleInvalidated[nftContract][tokenId][msg.sender][seller][amount][deadline]) { revert NFTMarketPrivateSale_Signature_Canceled_Or_Already_Claimed(); } privateSaleInvalidated[nftContract][tokenId][msg.sender][seller][amount][deadline] = true;
🌟 Selected for report: Dravee
Also found by: 0x1f8b, CertoraInc, TerrierLover, csanuragjain, gzeon, iain, kenta, rfa, robee, thankthedark
Reading array length on each iteration spends more gas instead of caching the array len outside of the loop:
Use != 0 instead of > 0 for unsigned integer comparisons:
The DIV opcode uses more gas in comparison to other viable options, such as the SHR opcode:
Initializing variable with the default value is unnecessary and costs additional gas:
#0 - HardlyDifficult
2022-03-03T12:56:42Z
Thanks for the feedback!
NFTMarketCreators
- it may offer a small gas savings, but it also improves readability since length is accessed multiple times there. We are going to pass on changing the NFTMarketReserveAuction
instance at this time though since that's a call reserved for Foundation admins and we are not concerned with optimizations there.#1 - alcueca
2022-03-17T16:09:00Z
Score: 500