Changes made to originOfferInfo
are lost because they are incorrectly stored in memory, causing a loss of collateral tokens for protocol users when the original makers of an AskOffer in turbo mode settle.
In PreMarkets::listOffer
, the function updates the abortOfferStatus
of the originOfferInfo
to establish the connection between the newly created offer and its parent offer. However, the state update is incorrectly implemented.
As shown in the code snippet above, the originOfferInfo
is being updated in memory, meaning the change is lost once the function finishes execution. As a result, originOfferInfo.abortOfferStatus
remains set to Initialized
(default status).
The Issue:
The abortOfferStatus
is used in the validation process to allow or disallow Ask makers from aborting their offers in PreMarkets::abortAskOffer
. Failing to properly update the originOfferInfo.abortOfferStatus
will cause a loss of funds for the protocol when a sub-offer that has sold its points is aborted. The taker who buys the points can then abort their stock. Takers are not allowed to abort their stock unless their settling maker has aborted their offer, as seen below.
This constraint is in place to avoid a scenario where a taker aborts their stock and retrieves the initial deposit, while the associated maker still pays point tokens for their greater-than-zero used points.
Scenario: I know this isn't the most straightforward concept to explain, but follow me here.
Action 1: User_A calls PreMarkets::createOffer
to create an Ask Offer for 1000 points in turbo mode.
User_A's offerInfo.points: 1000
User_A's offerInfo.usedPoints: 0 // No points sold yet
User_A's offerInfo.abortOfferStatus: AbortOfferStatus.Initialized
Action 2: User_B calls PreMarkets::createTaker
to buy 500 points from User_A and later calls PreMarkets::listOffer
to sell these 500 points
User_A's offerInfo.points: 1000
User_A's offerInfo.usedPoints: 500 // Points sold to User_B
User_A's offerInfo.abortOfferStatus: AbortOfferStatus.Initialized // The issue: remains Initialized
instead of changing to SubOfferListed
User_B's offerInfo.points: 500 // Points bought from User_A
User_B's offerInfo.usedPoints: 0 // No points sold yet
User_B's offerInfo.abortOfferStatus: AbortOfferStatus.Initialized // Newly listed stocks are always set to Initialized
Action 3: User_C calls PreMarkets::createTaker
to buy 500 points from User_B
Note: User_C only has a stock as they haven't listed their points yet.
User_A's offerInfo.points: 1000
User_A's offerInfo.usedPoints: 500 // Points sold to User_B
User_A's offerInfo.abortOfferStatus: AbortOfferStatus.Initialized // The issue: remains Initialized
instead of changing to SubOfferListed
User_B's offerInfo.points: 500 // Points bought from User_A
User_B's offerInfo.usedPoints: 500 // Points sold to User_C
User_B's offerInfo.abortOfferStatus: AbortOfferStatus.Initialized // Still Initialized
as this is not the original offer
Action 4: User_B calls PreMarkets::abortAskOffer
to abort their offer
Note: The protocol lets them abort their offer, as turbo makers are not obligated to settle anyone.
User_B's offerInfo.points: 500 // Points bought from User_A
User_B's offerInfo.usedPoints: 500 // Points sold to User_C
User_B's offerInfo.abortOfferStatus: AbortOfferStatus.Aborted // AbortOfferStatus updates correctly
Action 5: User_C calls PreMarkets::abortBidTaker
to abort their stock
Ideally, the protocol should not allow this abortion because the original offerInfo.usedPoints are still > 0, and the associated maker is obligated to settle User_C.
Settling Ask makers with offerInfo.usedPoints greater than zero are always charged point tokens and/or collateral tokens when settling.
The problem here is that User_C exits the trade, retrieving their initial deposit, while the maker of the original offer is still obligated to deposit point tokens to access their collateral.
This leads to a loss of tokens for the protocol. The final withdrawal of the collateral type from the protocol bears the brunt of the loss.
Manual
Update the originOfferInfo
in storage.
Valid high severity, because the `abortOfferStatus` of the offer is not updated and persist through `storage` when listing an offer for turbo mode within the `offerInfoMap` mapping, it allows premature abortion given the `abortOfferStatus` defaults to `Initialized`, allowing the bypass of this [check](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L552-L557) here and allow complete refund of initial collateral + stealing of trade tax which can potentially be gamed for profits using multiple addresses
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.