Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: low
Invalid

Makers Unable to Retrieve Remaining Collateral Funds When Offer Is Not Closed Before Market Update

Summary

In the Tadle protocol's PreMarkets.sol and DeliveryPlace.sol contracts, makers are unable to retrieve their remaining collateral funds if they do not close their offer before the market is updated. This issue specifically arises when the maker either never settles the offer or does not settle it sufficiently. In such cases, the remaining collateral funds become inaccessible due to the dependency on the offer's closure before a market update.

Vulnerability Details

The issue occurs because the functions responsible for handling collateral funds (e.g., settleAskMaker and closeBidTaker) depend on the offer being settled and closed by the maker. If the maker fails to close the offer before a market update, specifically when they either never settle the offer or do not settle it sufficiently, the remaining collateral funds that should be available to the maker are not accessible.

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

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

This scenario is problematic, as it relies on the maker's timely action to both settle and close the offer. If the market is updated before the maker closes the offer, the system does not provide a mechanism to access the remaining collateral funds, leading to a financial loss for the maker.

Impact

Makers lose access to their remaining collateral funds if they fail to settle and close their offer before a market update. This can lead to financial loss and negatively impact the maker's experience within the platform. This behavior could result in user dissatisfaction and a lack of trust in the platform's ability to manage funds correctly.

Proof of Concept

This is the test code.

function test_ask_offer_turbo_usdc() public {
console2.log("user usdc balance", mockUSDCToken.balanceOf(user));
console2.log("user1 usdc balance", mockUSDCToken.balanceOf(user1));
console2.log("");
vm.prank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
vm.startPrank(user1);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 500);
vm.stopPrank();
console2.log("user usdc balance", mockUSDCToken.balanceOf(user));
console2.log("user1 usdc balance", mockUSDCToken.balanceOf(user1));
console2.log("");
vm.prank(user1);
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
0.01 * 1e18,
block.timestamp - 1,
3600
);
vm.startPrank(user);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
deliveryPlace.settleAskMaker(offerAddr, 200);
vm.stopPrank();
vm.prank(user1);
address stock1Addr = GenerateAddress.generateStockAddress(1);
deliveryPlace.closeBidTaker(stock1Addr);
capitalPool.approve(address(mockUSDCToken));
capitalPool.approve(address(mockPointToken));
vm.startPrank(user);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.TaxIncome);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.ReferralBonus);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.SalesRevenue);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.RemainingCash);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.MakerRefund);
vm.stopPrank();
vm.startPrank(user1);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.TaxIncome);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.ReferralBonus);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.SalesRevenue);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.RemainingCash);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.MakerRefund);
vm.stopPrank();
console2.log("user usdc balance", mockUSDCToken.balanceOf(user));
console2.log("user1 usdc balance", mockUSDCToken.balanceOf(user1));
}

The result of this code is like this.

user usdc balance 100000000000000000000000000
user1 usdc balance 100000000000000000000000000
user usdc balance 99999999988000000000000000
user1 usdc balance 99999999994825000000000000
user usdc balance 99999999993150000000000000
user1 usdc balance 100000000000825000000000000

Before updating market, the maker (user) payed 0.012 ether for collateral and the taker (user1) payed 0.005175 ether for sales and fee. After settlement of offer, the taker get 0.006 ether for collateral. And the maker get 0.005 ether for sales and 0.00015 for maker bonus. But two users both didn't get remaining 0.006 ether collateral.

Tools Used

Manual code review

Recommendations

Modify the contract logic to ensure that remaining collateral funds are accessible even if the offer is not closed before a market update, particularly in cases where the offer is not fully settled.

Updates

Lead Judging Commences

0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

[invalid] finding-DeliveryPlace-owner-do-not-call-settleAskMaker

Invalid, the makers are incentivized to settle offers to earn maker bonuses when subsequent takers and makers make trade using the original collateral put up for points as well as get back their initial collateral. Additionally, if they do not settle on time, they will lose all their initial collateral, forcing the `owner` to come in and perform the settlement and retrieving that collateral. This is noted as a design decision [here](https://tadle.gitbook.io/tadle/how-tadle-works/features-and-terminologies/settlement-and-collateral-rate) If all else fails, the `owner` can come in to settle as seen [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L254-L256) and [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L365-L367) offers to allow closing offers and subsequently allowing refunds. I acknowledge that perhaps a more decentralized

Support

FAQs

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