Tadle

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

Buyers will never receive the pointToken after settlement

Summary

Buyers will never receive the pointToken after settlement

Vulnerability Details

DeliveryPlace:settleAskTaker() is intended to be called by a BidMaker in order to settle the trade with his AskTaker where:

  • the BidMaker will receive his tokenPoints

  • the AskTaker will receive the collateral from the BidMaker

As we can see the function will first compute the amount of pointToken the buyer is entitled to by multipling them with tokenPerPoint:

uint256 settledPointTokenAmount = marketPlaceInfo.tokenPerPoint * _settledPoints;

tokenPerPoint, configured by the admin post-tge, specifies the conversion rate from points to pointToken.

The issue is found in the fact that the function incorrectly accredits makerInfo.tokenAddress, which is the collateral token , as the pointToken:

tokenManager.addTokenBalance(
TokenBalanceType.PointToken,
_msgSender(),
makerInfo.tokenAddress, // @audit-issue should be the pointToken
pointTokenAmount
);

The same issue is found in the counter-part function, DeliveryPlace:closeBidTaker(), which is for Ask offers.

Impact

The buyer will receive WETH/USDC instead of the pointToken after the settlement.

Additionally, if the makerInfo.tokenAddress is USDC, (which has 6 decimals) the recipient will massive amounts of USDC since settledPointTokenAmount has 18 decimals of precision.

POC

Add the following test to PreMarket.t.sol:

function test_incorrect_addTokenBalance_usd_settleAskTaker() public {
vm.label(user, "buyer");
vm.label(user2, "seller");
vm.label(address(mockUSDCToken), "USDC");
vm.label(address(mockPointToken), "PointToken");
capitalPool.approve(address(mockUSDCToken));
// user wants to buy 1000 points for 5 USDC
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000, // 1000 points
5e6, // 5 USDC
10000, // 100% collateral rate
300,
OfferType.Bid,
OfferSettleType.Protected
)
); // -> offer0Addr
vm.startPrank(user2);
address offer0Addr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offer0Addr, 1000); // -> stock1Addr
vm.startPrank(user1); // owner setup market
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
1e18, // 1 point = 1 pointToken
block.timestamp - 1,
3600
);
vm.startPrank(user);
address stock1Addr = GenerateAddress.generateStockAddress(1);
mockPointToken.approve(address(tokenManager), type(uint256).max);
deliveryPlace.settleAskTaker(stock1Addr, 1000);
tokenManager.withdraw(address(mockPointToken), TokenBalanceType.PointToken); // 0
vm.expectRevert(); // it will try to tansfer 10e19 USDC
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.PointToken);
}

The buyer, who should've been credited with 1000 pointTokens, is instead credited with 1000e18 USDC.

In this case the transaction will revert because this is a massive amount of USDC since the token has 6 decimals.

Tools Used

  • Manual review

  • Foundry

Recommendations

Change the PointToken accrediting logic in both DeliveryPlace:settleAskTaker() and DeliveryPlace:closeBidTaker().

tokenManager.addTokenBalance(
TokenBalanceType.PointToken,
offerInfo.authority,
- makerInfo.tokenAddress,
+ marketPlaceInfo.tokenAddress,
settledPointTokenAmount
);
Updates

Lead Judging Commences

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

finding-DeliveryPlace-settleAskTaker-closeBidTaker-wrong-makerinfo-token-address-addToken-balance

Valid high severity, In `settleAskTaker/closeBidTaker`, by assigning collateral token to user balance instead of point token, if collateral token is worth more than point, this can cause stealing of other users collateral tokens within the CapitalPool contract, If the opposite occurs, user loses funds based on the points they are supposed to receive

Support

FAQs

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