Tadle

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

High Collateral Rates Without Payment Enable CapitalPool Draining

Summary

Any bid-makers can drain the whole capitalPool by assigning themselves high collateral

Vulnerability Details

The bid-makers shouldn't provide collaterals to create an offer and the protocol should charge them to only transfer in the amount of the params.amount.

// PreMarkets::createOffer()
{
/// @dev transfer collateral from _msgSender() to capital pool
uint256 transferAmount = OfferLibraries.getDepositAmount(
params.offerType,
params.collateralRate,
params.amount,
true,
Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.tillIn{value: msg.value}(
_msgSender(),
params.tokenAddress,
transferAmount,
false
);
}
function getDepositAmount(
OfferType _offerType,
uint256 _collateralRate,
uint256 _amount,
bool _isMaker,
Math.Rounding _rounding
) internal pure returns (uint256) {
/// @dev bid offer
if (_offerType == OfferType.Bid && _isMaker) {
return _amount;
}
/// @dev ask order
if (_offerType == OfferType.Ask && !_isMaker) {
return _amount;
}
return
Math.mulDiv(
_amount,
_collateralRate,
Constants.COLLATERAL_RATE_DECIMAL_SCALER,
_rounding
);
}

However, the createOffer function still assigns the params.collateralRate to the OfferInfo.collateral for the bid-makers which it shouldn't cause they just transferred in the amount of params.amount not the params.amount * params.collateralRate

// PreMarkets::createOffer()
/// @dev update offer info
offerInfoMap[offerAddr] = OfferInfo({
id: offerId,
authority: _msgSender(),
maker: makerAddr,
offerStatus: OfferStatus.Virgin,
offerType: params.offerType,
points: params.points,
amount: params.amount,
>>> collateralRate: params.collateralRate,
abortOfferStatus: AbortOfferStatus.Initialized,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});

This issue will let the bid-makers drain the capitalPool by simply calling the DeliveryPlace::settleAskTaker after the updated market

// DeliveryPlace::settleAskTaker()
uint256 collateralFee = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
stockInfo.amount,
false,
Math.Rounding.Floor
);
if (_settledPoints == stockInfo.points) {
tokenManager.addTokenBalance(
TokenBalanceType.RemainingCash,
_msgSender(),
makerInfo.tokenAddress,
>>> collateralFee
);
}

they could provide a high coll without paying it and the protocol assigns them collateralFee as remaining cash which they could simply withdraw, leading to loss of funds

this test demonstrates the attack path by adding it to the PreMarkets.t.sol:

function test_bidMakersDrainCapitalPool() public {
deal(address(mockUSDCToken), address(capitalPool), 10 ether);
address _offer1 = GenerateAddress.generateOfferAddress(0);
address _stock2 = GenerateAddress.generateStockAddress(1);
uint256 userUSDCBalBef = mockUSDCToken.balanceOf(user);
uint256 capitalPoolUSDCBalBef = mockUSDCToken.balanceOf(address(capitalPool));
vm.prank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
1e7, // putting high coll rate
300,
OfferType.Bid,
OfferSettleType.Protected
));
vm.prank(user2);
preMarktes.createTaker(_offer1, 1000);
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.settleAskTaker(_stock2, 1000);
vm.stopPrank();
uint256 remainingCashOfBidMaker = tokenManager.userTokenBalanceMap(
address(user),
address(mockUSDCToken),
TokenBalanceType.RemainingCash
);
assertEq(remainingCashOfBidMaker, 10 ether);
}

Impact

bid-makers can drain the whole capitalPool by providing a high collateral without paying for it

Tools Used

manual review

Recommendations

In the PreMarkets::createOffer() function, you must put the collateral 0 when updating the offerInfo on bid mode:

offerInfoMap[offerAddr] = OfferInfo({
id: offerId,
authority: _msgSender(),
maker: makerAddr,
offerStatus: OfferStatus.Virgin,
offerType: params.offerType,
points: params.points,
amount: params.amount,
+ collateralRate: params.offerType == OfferType.Bid
? 0
: params.collateralRate,
abortOfferStatus: AbortOfferStatus.Initialized,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});
Updates

Lead Judging Commences

0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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