Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: high
Valid

It is possible to abort an Turbo offer even when a suboffer exists.

Summary

When a user creates an Turbo ASK offer, and another user (user1) creates a taker on the original offer and lists this offer to create a SubOffer, the original ASK offer should not be allowed to be aborted by the user.

Vulnerability Details

When a user lists an ASK Turbo offer, the status of the original offer should change to SubOfferListed, but this is not happening.

Add the code below to the Test contract PreMarketsTest : PreMarketsTest.t.sol

function test_abort_Ask_offer() public {
//1 - user create an offer
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace, address(mockUSDCToken), 1000, 0.01 * 1e18, 12000, 300, OfferType.Ask, OfferSettleType.Turbo
)
);
vm.stopPrank();
//2 - user1 send USDC to the pool to create a taker
vm.startPrank(user1);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
address stockAddr = GenerateAddress.generateStockAddress(0);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 500);
vm.stopPrank();
//3 - user1 list the offer
vm.prank(user1);
address stock1Addr = GenerateAddress.generateStockAddress(1);
preMarktes.listOffer(stock1Addr, 0.006 * 1e18, 12000);
//4 - user should not be able to abort the offer, because user1 list the offer
vm.expectRevert();
vm.prank(user);
preMarktes.abortAskOffer(stockAddr, offerAddr);
}

Run the command bellow

forge test --mc PreMarketsTest --mt test_abort_Ask_offer -vvvv

The test should revert, but it does not.

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 11.95ms (2.04ms CPU time)
Ran 1 test suite in 1.03s (11.95ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/PreMarkets.t.sol:PreMarketsTest
[FAIL. Reason: call did not revert as expected] test_abort_Ask_offer() (gas: 1083004)

Impact

The user who created the SubOffer is unable to negotiate their listed offer or the original Turbo ASK offer can't settled.
The user who created the SubOffer is disadvantaged.

Tools Used

foundry & manual review

Recommendations

To update the status and prevent a listed offer from being aborted, the following modifications need to be applied

function listOffer(address _stock, uint256 _amount, uint256 _collateralRate) external payable {
if (_amount == 0x0) {
revert Errors.AmountIsZero();
}
if (_collateralRate < Constants.COLLATERAL_RATE_DECIMAL_SCALER) {
revert InvalidCollateralRate();
}
StockInfo storage stockInfo = stockInfoMap[_stock];
console.log("stockInfo.authoroty", stockInfo.authority);
if (_msgSender() != stockInfo.authority) {
revert Errors.Unauthorized();
}
OfferInfo storage offerInfo = offerInfoMap[stockInfo.preOffer];
MakerInfo storage makerInfo = makerInfoMap[offerInfo.maker];
/// @dev market place must be online
ISystemConfig systemConfig = tadleFactory.getSystemConfig();
MarketPlaceInfo memory marketPlaceInfo = systemConfig.getMarketPlaceInfo(makerInfo.marketPlace);
marketPlaceInfo.checkMarketPlaceStatus(block.timestamp, MarketPlaceStatus.Online);
if (stockInfo.offer != address(0x0)) {
revert OfferAlreadyExist();
}
if (stockInfo.stockType != StockType.Bid) {
revert InvalidStockType(StockType.Bid, stockInfo.stockType);
}
/// @dev change abort offer status when offer settle type is turbo
if (makerInfo.offerSettleType == OfferSettleType.Turbo) {
address originOffer = makerInfo.originOffer;
+ OfferInfo storage originOfferInfo = offerInfoMap[originOffer];
- OfferInfo memory originOfferInfo = offerInfoMap[originOffer];
if (_collateralRate != originOfferInfo.collateralRate) {
revert InvalidCollateralRate();
}
originOfferInfo.abortOfferStatus = AbortOfferStatus.SubOfferListed;
}
/// @dev transfer collateral when offer settle type is protected
if (makerInfo.offerSettleType == OfferSettleType.Protected) {
uint256 transferAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType, offerInfo.collateralRate, _amount, true, Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.tillIn{value: msg.value}(_msgSender(), makerInfo.tokenAddress, transferAmount, false);
}
address offerAddr = GenerateAddress.generateOfferAddress(stockInfo.id);
if (offerInfoMap[offerAddr].authority != address(0x0)) {
revert OfferAlreadyExist();
}
/// @dev update offer info
offerInfoMap[offerAddr] = OfferInfo({
id: stockInfo.id,
authority: _msgSender(),
maker: offerInfo.maker,
offerStatus: OfferStatus.Virgin,
offerType: offerInfo.offerType,
abortOfferStatus: AbortOfferStatus.Initialized,
points: stockInfo.points,
amount: _amount,
collateralRate: _collateralRate,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});
stockInfo.offer = offerAddr;
emit ListOffer(offerAddr, _stock, _msgSender(), stockInfo.points, _amount);
}
Updates

Lead Judging Commences

0xnevi Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-PreMarkets-listOffer-originIOfferInfo-storage-memory

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

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.