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: 5/28
Findings: 2
Award: $4,653.83
🌟 Selected for report: 1
🚀 Solo Findings: 0
3652.1324 USDC - $3,652.13
https://github.com/code-423n4/2022-02-foundation/blob/4d8c8931baffae31c7506872bf1100e1598f2754/contracts/mixins/NFTMarketReserveAuction.sol#L325-L349 https://github.com/code-423n4/2022-02-foundation/blob/4d8c8931baffae31c7506872bf1100e1598f2754/contracts/mixins/NFTMarketReserveAuction.sol#L596-L599
NFT owner can permanently lock funds of bidders.
Alice (the attacker) calls createReserveAuction
, and creates one like normal. let this be auction id 1.
Alice calls createReserveAuction
again, before any user has placed a bid (this is easy to guarantee with a deployed attacker contract). We'd expect that Alice wouldn't be able to create another auction, but she can, because _transferToEscrow
doesn't revert if there's an existing auction. let this be Auction id 2.
Since nftContractToTokenIdToAuctionId[nftContract][tokenId]
will contain auction id 2, all bidders will see that auction as the one to bid on (unless they inspect contract events or data manually).
Alice can now cancel auction id 1, then cancel auction id 2, locking up the funds of the last bidder on auction id 2 forever.
Prevent NFT owners from creating multiple auctions
#0 - HardlyDifficult
2022-02-28T20:03:43Z
This is a great find!
The impact of this bug is:
We have fixed this problem by adding the following code to createReserveAuction
:
// This check must be after _transferToEscrow in case auto-settle was required if (nftContractToTokenIdToAuctionId[nftContract][tokenId] != 0) { revert NFTMarketReserveAuction_Already_Listed(nftContractToTokenIdToAuctionId[nftContract][tokenId]); }
🌟 Selected for report: leastwood
Also found by: 0x1f8b, 0xliumin, 0xwags, CertoraInc, Dravee, IllIllI, Ruhum, TerrierLover, WatchPug, cmichel, csanuragjain, defsec, gzeon, hubble, jayjonah8, kenta, kirk-baird, rfa, robee
1001.7004 USDC - $1,001.70
QA Report
Overall I would recommend rethinking the architecture here. Some of the abstract contracts are leaky abstractions and have many cross dependencies.
Just an idea, have one contract that serves as a vault for the NFTs and manages all information about escrow for buy price / auction. That way there isn't so much state that you have to manage between all the different contracts and there's one contract that manages whether a given NFT is in escrow.
It's best practice to use this functions so NFTs don't get transferred to contracts that aren't aware of the protocol. Make sure you account for reentrancy though.
All the large unchecked blocks can potentially cause unexpected overflow issues in arithmetic. Make sure very unchecked block targets a very specific block of code (as opposed to large for loop blocks).
These should be revokeRole
, not grantRole
https://github.com/code-423n4/2022-02-foundation/blob/4d8c8931baffae31c7506872bf1100e1598f2754/contracts/mixins/roles/AdminRole.sol#L35
#0 - HardlyDifficult
2022-02-28T20:13:38Z
Thanks for the feedback!
safeTransferFrom
: This is a common recommendation but it does increase exposure to potential reentrancy issues. We do use nonReentrant
for many calls, but we'd still like to limit external calls where possible just in case the modifier was missed in one of our flows. I'm not currently aware of a good use case which would require this -- but it is certainly something we are considering adding in the future.++i
portion of a for loop, we need to wrap the entire loop as unchecked since Solidity does not yet have a way of scoping the unchecked section in-line. Once that capability is added, we will clean up the code to make unchecked blocks more targeted and easier to reason about.#1 - alcueca
2022-03-17T09:19:44Z
Unadjusted score: 55 (inclidung 30 points for the extensive architecture recommendations).
#2 - alcueca
2022-03-17T10:01:15Z
Extra 5 points from #24
#3 - alcueca
2022-03-18T11:07:06Z
Extra 5 points from #22.