Platform: Code4rena
Start Date: 16/12/2022
Pot Size: $32,070 USDC
Total HM: 18
Participants: 5
Period: 3 days
Judge: hickuphh3
Total Solo HM: 5
Id: 195
League: ETH
Rank: 3/5
Findings: 3
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
Data not available
In the OptimisticListingSeaport contract, any user with the Rae token corresponding to this Vault can call the propose function to create a listing proposal, which can be used for listing when the proposal exists over PROPOSAL_PERIOD.
function list(address _vault, bytes32[] calldata _listProof) public { // Reverts if vault is not registered (address token, uint256 id) = _verifyVault(_vault); // Reverts if vault is not current owner of the assets if (_verifySale(_vault)) revert NotOwner(); // Reverts if collateral of proposed listing has been rejected Listing storage proposedListing = proposedListings[_vault]; if (proposedListing.collateral == 0) revert Rejected(); // Reverts if proposal period has not elapsed if (proposedListing.proposalDate + PROPOSAL_PERIOD > block.timestamp) revert TimeNotElapsed();
Alternatively, any user can call the rejectProposal function to buy out the proposal, at which point the proposal will be reset
if (proposedListing.collateral == 0) { // Resets proposed listing to default _setListing(proposedListing, address(this), 0, type(uint256).max, 0); }
This allows a malicious user to call the propose function to create a new proposal before the listing proposal is passed, and then immediately call rejectProposal to cancel the proposal, thereby indefinitely delaying the passing of the listing proposal
Consider the following scenario where alice has created a listing proposal and needs to wait 4 days for the proposal to pass On the third day, bob creates a proposal with a lower price and immediately calls rejectProposal to cancel the proposal, at which point alice's proposal is reset, alice needs to create a new proposal and still wait 4 days, and bob can continue to repeat the previous actions to indefinitely delay the listing of the proposal.
https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/seaport/modules/OptimisticListingSeaport.sol#L123-L124 https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/seaport/modules/OptimisticListingSeaport.sol#L156-L160 https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/seaport/modules/OptimisticListingSeaport.sol#L218-L227
None
Consider trying to resume the previous proposal in the rejectProposal function when the proposal is cancelled, and increasing the submission time of the previous proposal appropriately so as to avoid having the proposal already passed when it is resumed
#0 - HickupHH3
2022-12-20T02:50:25Z
similar to #41 in DoSing a listing proposal, but with a diff method.
#1 - trust1995
2022-12-21T06:20:56Z
Yeah, the exact method is dup of #42
#2 - c4-judge
2022-12-23T01:14:00Z
HickupHH3 marked the issue as duplicate of #25
#3 - c4-judge
2022-12-23T01:14:04Z
HickupHH3 marked the issue as satisfactory
Data not available
In the contribute() function of the GroupBuy contract, even if the filledQuantity is less than _quantity, all the ETH provided by the user is locked in the contract, and the user can only call claim() to get it back after purchasing NFT or Pool expires, which leads to the contract locking too much of the user's ETH.
function contribute( uint256 _poolId, uint256 _quantity, uint256 _price ) public payable { // Reverts if pool ID is not valid _verifyPool(_poolId); // Reverts if NFT has already been purchased OR termination period has passed (, uint48 totalSupply, , , ) = _verifyUnsuccessfulState(_poolId); // Reverts if ether contribution amount per Rae is less than minimum bid price per Rae if (msg.value < _quantity * minBidPrices[_poolId] || _quantity == 0) revert InvalidContribution(); // Reverts if ether payment amount is not equal to total amount being contributed if (msg.value != _quantity * _price) revert InvalidPayment(); // Updates user and pool contribution amounts userContributions[_poolId][msg.sender] += msg.value; totalContributions[_poolId] += msg.value;
Consider the following scenario Pool's totalSupply is 100, minBidPrices is 1 ETH, and _duration is 30 days. alice is ready to call the contribute function to contribute 50 ETH to the Pool But because another user called the contribute function first, when alice calls contribute, the remainingSupply in the Pool is 1. This causes alice's remaining 49 ETH to be locked in the contract, and since there is no corresponding NFT for sale in the market, alice's remaining 49 ETH will be locked for 30 days
None
Consider that in contribute(), when filledQuantity is less than _quantity, refund the excess ETH to the user
#0 - c4-judge
2022-12-20T04:36:19Z
HickupHH3 marked the issue as satisfactory
#1 - c4-judge
2022-12-20T04:36:23Z
HickupHH3 marked the issue as primary issue
#2 - c4-judge
2022-12-21T17:58:30Z
HickupHH3 marked the issue as selected for report
#3 - IllIllI000
2022-12-21T18:05:08Z
@HickupHH3 shouldn't this be invalid since funds aren't locked and can be withdrawn as expected?
#4 - HickupHH3
2022-12-21T18:19:07Z
Note that this issue makes no assumption about price, only about partial filling. In the case of a partial fill, we can break this down into 2 cases:
_price >= lowestBid
_price < lowestBid
(1) is invalid; funds aren't locked because the unfilled quantities will displace the lowest bids, so they're rightfully marked as contributions, even potentially displacing some bids from itself which will increment pendingBalances
.
(2) is the scenario described in #31
Therefore a very weak dup of #31 because the functional vuln is correctly identified (contributions lock up ETH), but didn't correctly pinpoint the conditions for it to occur.
#5 - c4-judge
2022-12-21T18:19:30Z
HickupHH3 marked the issue as duplicate of #31
#6 - c4-judge
2022-12-21T18:19:47Z
HickupHH3 marked the issue as partial-25
#7 - c4-judge
2022-12-21T18:19:53Z
HickupHH3 marked the issue as not selected for report
#8 - c4-judge
2023-01-11T02:27:20Z
HickupHH3 marked the issue as satisfactory
#9 - c4-judge
2023-01-13T15:25:45Z
HickupHH3 marked the issue as partial-25
Data not available
Judge has assessed an item in Issue #27 as M risk. The relevant finding follows:
Dup of #45. The effect is severe enough IMO to warrant a med severity although I initially intepreted it as a spec mismatch.
#0 - c4-judge
2022-12-20T07:44:55Z
HickupHH3 marked the issue as duplicate of #45
#1 - c4-judge
2022-12-20T07:45:26Z
HickupHH3 marked the issue as satisfactory
Data not available
In the _constructOrder function, the feeReceiver is added to the seaport order, and when the user fulfills the order, tesseraFees amount of ETH is sent to the feeReceiver, which means that a malicious feeReceiver can prevent the listing from being purchased by disabling the receipt of ETH
orderParams.consideration.push( ConsiderationItem(ItemType.NATIVE, address(0), 0, tesseraFees, tesseraFees, feeReceiver) ); uint256 counter = ISeaport(seaport).getCounter(_vault); vaultOrderHash[_vault] = _getOrderHash(orderParams, counter);
https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/seaport/modules/OptimisticListingSeaport.sol#L422-L425 https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/seaport/modules/OptimisticListingSeaport.sol#L336-L339
None
consider using address(this) in the _constructOrder function as the receiving address for tesseraFees, after which the feeReceiver can collect tesseraFees from the contract
#0 - HickupHH3
2022-12-20T02:57:14Z
Disagree, feeReceiver
is set by the contract deployer (project), would be a centralisation / trust issue. Downgrading to QA.
#1 - c4-judge
2022-12-20T02:57:20Z
HickupHH3 changed the severity to QA (Quality Assurance)
#2 - c4-judge
2022-12-20T09:39:51Z
HickupHH3 marked the issue as grade-b
#3 - c4-sponsor
2023-01-05T18:35:14Z
mehtaculous marked the issue as sponsor acknowledged