Platform: Code4rena
Start Date: 16/12/2022
Pot Size: $60,500 USDC
Total HM: 12
Participants: 58
Period: 5 days
Judge: Trust
Total Solo HM: 4
Id: 196
League: ETH
Rank: 18/58
Findings: 1
Award: $460.00
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: yixxas
Also found by: 0x52, 0xAgro, 0xSmartContract, 0xhacksmithh, Aymen0909, Bnke0x0, Bobface, Breeje, Diana, Franfran, HE1M, HollaDieWaldfee, IllIllI, Jeiwan, RaymondFam, Rolezn, SaharDevep, Secureverse, SmartSek, ak1, bin2chen, brgltd, chrisdior4, gz627, imare, ladboy233, lukris02, oyc_109, rvierdiiev, shark, tnevler, unforgiven, wait
460.0033 USDC - $460.00
Currently, we have a decay per period of 70% and a period of 1 day. This means that every day, price will drop be 70%. While I understand that the protocol strives for an exponential decay dutch auction format, with the current numbers, price of NFT will quickly be negligible.
An example of how quickly the price can drop.
Day 0: 1000 Day 1: 300 Day 2: 90 Day 3: 27
One recommendation is to reduce this amount to a more reasonable 30-50%. It still maintains the property of exponential decrease, but at a slower pace.
Â
latestAuctionStartTime
can be wrongly set to 0 even if an NFT is still selling in auctionThis issue can happen when multiple collaterals are sent for auction. The vulnerability can happens due to _purchaseNFTAndUpdateVaultIfNeeded()
.
function _purchaseNFTAndUpdateVaultIfNeeded(Auction calldata auction, uint256 maxPrice, address sendTo) internal returns (uint256) { (uint256 startTime, uint256 price) = _purchaseNFT(auction, maxPrice, sendTo); if (startTime == _vaultInfo[auction.nftOwner][auction.auctionAssetContract].latestAuctionStartTime) { _vaultInfo[auction.nftOwner][auction.auctionAssetContract].latestAuctionStartTime = 0; } return price; }
We note how _vaultInfo[auction.nftOwner][auction.auctionAssetContract].latestAuctionStartTime
is set to 0 when startTime == _vaultInfo[auction.nftOwner][auction.auctionAssetContract].latestAuctionStartTime
.
It is documented that when latestAuctionStartTime == 0
, no auction is being held but this is not true.
2 collaterals from a user can be sent to an auction, but the later one gets purchased first. This would set the vault.latestAuctionStartTime
to 0 even though the first auction is still running. This can lead to potential problems in the future if we rely on this value.
It might be a good idea to restrict only one collateral to be sent to the auction at a time. Another high severity issue arises due to this as I have wrote in my other report.
Â
There is currently a huge difference in price between the top few PUNK NFT, and the bottom few. For instance, the lowest ask price is currently 63.66 ETH
( as of the time of writing this report ) as seen here. The top few NFTS are last sold in the range of 1000s of ETH.
Since the value of the debt that a user can raise from the collateral is computed by the total number of deposited collaterals multiplied by the lowest price of the NFT in the collection, it makes little sense of anyone to use any higher-valued NFTs as collateral. This seriously limits the use of the protocol if only a limited number of NFTS are used.
It is recommended that we use a different metric to measure price here. We could target NFTs indivudally instead of seeing them as a group.
Â
signerAddress
is not 0ecrecover
returns a value of 0 for invalid signature. We also note that in the constructor, there is no check to ensure that oracleSigner
is not address 0.
The only check for validity of signature is this,
if (signerAddress != oracleSigner) { revert IncorrectOracleSigner(); }
If oracleSigner
is set to address(0)
then a malicious user can pass any price it wants into oracleInfo
to bypass the check of signature used in underwritePriceForCollateral()
and hence liquidate any collateral of any user, as well as being able to purchase this liquidated NFT at a price of 0.
It is recommendated to add the check if(signerAddress == address(0)) revert Error()
.
Â
Liquidation is decided based on the 30 days TWAP of the floor price of the collection. This might be manipulatable as we only have to manipulate a single NFT to drastically decrease the maximum debt that a user can hold since calculation is done by
Total number of NFTS * floor price
An attacker can possibly control the price of a single NFT, and liquidate many users.
Â
Currently, the check for oracle timestamp to not exceed block.timestamp
is done with a strict comparison. Using <=
can be better here as VALID_FOR
implies that the oracle timestamp would be valid for the entire duration.
oracleInfo.message.timestamp + VALID_FOR < block.timestamp
Change to ``oracleInfo.message.timestamp + VALID_FOR <= block.timestamp`
#0 - c4-judge
2022-12-25T14:32:29Z
trust1995 marked the issue as grade-a
#1 - c4-judge
2023-01-06T21:20:27Z
trust1995 marked the issue as selected for report
#2 - wilsoncusack
2023-01-31T19:06:27Z
I disagree with L02 - latestAuctionStartTime can be wrongly set to 0 even if an NFT is still selling in auction
The intend of latestAuctionStartTime is to ensure min spacing between any two auctions. If two auctions are running, that means that minSpacing time has passed. If minSpacing time has passed, then it is OK to reset latestAuctionStartTime after purchasing the latest auction to allow a 3rd auction to start.