Tadle

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

Flow in DeliveryPlace.closeBidTaker allow users to create more point tokens than exists in the maker's offer

Summary

The DeliveryPlace.closeBidTaker function is used to allow the taker to receive the purchased point tokens after the maker's offer has been settled. However, there is a flow in the function that allows more point tokens to be created than the maker had put up for sale through the re-listing of the purchased tokens. As a result, after the offer is settled and closeBidTaker is executed for the respective takers, the balance in the tokenManager will show more point tokens than it should. Consequently, some users may be unable to withdraw the purchased tokens because there will not be enough available, or tokens intended for a different offer may be sent instead. Both cases lead to a loss of funds for users.

Vulnerability Details

The problem stems from this line in the closeBidTaker function where, if offerSettleType is Protected, usedPoints are not taken into account as they are in turbo mode.

if (makerInfo.offerSettleType == OfferSettleType.Protected) {
offerInfo = preOfferInfo;
@>userRemainingPoints = stockInfo.points;
} else {

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

I am attaching a proof of concept (POC) that demonstrates the issue.

POC

function test_ask_protected_chain() public {
vm.startPrank(user);
uint256 userUSDTBalance0 = mockUSDCToken.balanceOf(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
800,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Protected
)
);
uint256 userUSDTBalance1 = mockUSDCToken.balanceOf(user);
//assertEq(userUSDTBalance1, userUSDTBalance0 - 0.012 * 1e18);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
vm.startPrank(user1);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offerAddr, 300);
vm.stopPrank();
vm.startPrank(user2);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offerAddr, 500);
address stock2Addr = GenerateAddress.generateStockAddress(2);
preMarktes.listOffer(stock2Addr, 0.006 * 1e18, 12000);
vm.stopPrank();
address offer2Addr = GenerateAddress.generateOfferAddress(2);
vm.startPrank(user3);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offer2Addr, 200);
vm.stopPrank();
vm.startPrank(user);
address originStock = GenerateAddress.generateStockAddress(0);
address originOffer = GenerateAddress.generateOfferAddress(0);
preMarktes.closeOffer(originStock, originOffer);
preMarktes.relistOffer(originStock, originOffer);
vm.stopPrank();
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, 800);
vm.stopPrank();
vm.prank(user1);
address stock1Addr = GenerateAddress.generateStockAddress(1);
deliveryPlace.closeBidTaker(stock1Addr);
vm.prank(user2);
deliveryPlace.closeBidTaker(stock2Addr);
vm.prank(user2);
deliveryPlace.settleAskMaker(offer2Addr, 200);
vm.prank(user3);
address stock3Addr = GenerateAddress.generateStockAddress(3);
deliveryPlace.closeBidTaker(stock3Addr);
console.log("User 1 TokenBalanceType.PointToken: %d", tokenManager.userTokenBalanceMap(
address(user1),
address(mockUSDCToken),
TokenBalanceType.PointToken
));
console.log("User 2 TokenBalanceType.PointToken: %d", tokenManager.userTokenBalanceMap(
address(user2),
address(mockUSDCToken),
TokenBalanceType.PointToken
));
console.log("User 3 TokenBalanceType.PointToken: %d", tokenManager.userTokenBalanceMap(
address(user3),
address(mockUSDCToken),
TokenBalanceType.PointToken
));
}
User 1 TokenBalanceType.PointToken: 3000000000000000000
User 2 TokenBalanceType.PointToken: 5000000000000000000
User 3 TokenBalanceType.PointToken: 2000000000000000000

It can be seen that 800 points are initially created with the maker's offer but at the end of the test there are 1000 in the balances of the users.

Impact

Loss of funds for the users and profit for the exploiter.

Tools Used

Manual review

Recommendations

Get into consideration usedPoints like did for the turbo mode.

Updates

Lead Judging Commences

0xnevi Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

[invalid] finding-PreMarkets-closeBidTaker-userRemaining-points-wrong-set

Valid high, regardless for turbo or protected mode, partial settlements are possible as seen [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L286-L299). For protected mode, partial settlements are not accounted for, allowing more then intended points to be sent to users even when maker only performed a partial settlement Note for invalidation: Agree with the discussions above that this issue is invalid.  Protected Mode is a step-by-step process. Example: > A created a purchase order for 800 points, B bought 300 points of it, and C bought 500 points of it. C re-listed 500 points, and D bought 200 points of them. > Settlement phase. A settles with B and C. C settles with D. A pays 800 point tokens, and C pays 200 point tokens to D, so the total Balance will have 1000 point tokens. Additionally, for any maker that does not settle, they will lose their original collateral posted in protected mode as it will force the admin to step in to settle.

Appeal created

cryptomoon Auditor
10 months ago
0xbrivan2 Auditor
10 months ago
cryptomoon Auditor
10 months ago
cryptomoon Auditor
10 months ago
0xbrivan2 Auditor
10 months ago
cryptomoon Auditor
10 months ago
0xbrivan2 Auditor
10 months ago
ge6a Submitter
10 months ago
0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

[invalid] finding-PreMarkets-closeBidTaker-userRemaining-points-wrong-set

Valid high, regardless for turbo or protected mode, partial settlements are possible as seen [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L286-L299). For protected mode, partial settlements are not accounted for, allowing more then intended points to be sent to users even when maker only performed a partial settlement Note for invalidation: Agree with the discussions above that this issue is invalid.  Protected Mode is a step-by-step process. Example: > A created a purchase order for 800 points, B bought 300 points of it, and C bought 500 points of it. C re-listed 500 points, and D bought 200 points of them. > Settlement phase. A settles with B and C. C settles with D. A pays 800 point tokens, and C pays 200 point tokens to D, so the total Balance will have 1000 point tokens. Additionally, for any maker that does not settle, they will lose their original collateral posted in protected mode as it will force the admin to step in to settle.

Support

FAQs

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