Tadle

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

Malicious user can rug-pull/honey pot the honest users/protocol as there is `no` incentive for malicious user to call `delivery::settleAskMaker()` offer

Summary

Malicious user can rug pull or honey pot the honest users/protocol because there is no incentive for malicious user to call delivery::settleAskMaker() offer

Vulnerability Details

A user can create ask offer using preMarket:createOffer() by giving collateral. When another user creates taker using preMarket::createTaker() then originalOffer's owner gets tradeTax & salesRevenue

function createTaker(address _offer, uint256 _points) external payable {
...
/// @dev Transfer token from user to capital pool as collateral
uint256 depositAmount = _points.mulDiv(offerInfo.amount, offerInfo.points, Math.Rounding.Ceil);
uint256 platformFee = depositAmount.mulDiv(platformFeeRate, Constants.PLATFORM_FEE_DECIMAL_SCALER);
uint256 tradeTax = depositAmount.mulDiv(makerInfo.eachTradeTax, Constants.EACH_TRADE_TAX_DECIMAL_SCALER);
...
@> _updateTokenBalanceWhenCreateTaker(_offer, tradeTax, depositAmount, offerInfo, makerInfo, tokenManager);
}
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
);
}
}

Now the problem is, tradeTax + salesRevenue generated for originalOffer's owner is greater than the collateralAmount that owner put in initially while creating ask offer. This tradeTax + salesRevenue can be withdrawn by owner immediately leaving no incentive for owner to call settleAskMaker().

If owner didn't call settleAskMaker() then his initialCollateral will be used to pay the buyers, but the problem is buyers will only get transferdAmount(which was actually paid to buy points) not the tradeTax & platformFee which they pay while creating taker, which is loss for the honest users.

A malicious user can create ask offer for a popular maketPlace with low amount & high tradeTax and let people trade for those points. Once all points are sold or enough(more than initial depositCollateral) tradeTax & salesRevenue is generated then malicious user can withdraw all token & never call settleAskMaker(), making a loss for buyer with tradeTax & platformFee

//Here is PoC which clearly shows owner gets more tokens than he deposited initially as collateral, also he can withdraw those tokens & never call settleAskMaker(), successfully rug pulling honest users

Note:- I've not shown withdrawing those token in this test because tokenManager:withdraw() is broken & I've submitted that as different issue. If we fix those issue we can withdraw.

function test_MaliciousUserCanRugPull() public {
//Suppose user is malicious & created an offer of 1000 points with 1000e18 amount at 10000(100%) collateralRate
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace, address(mockUSDCToken), 1000, 1000e18, 10000, 300, OfferType.Ask, OfferSettleType.Turbo
)
);
vm.stopPrank();
uint256 amountPaidByUser = mockUSDCToken.balanceOf(address(capitalPool));
console2.log("Amount paid by malicious user:", amountPaidByUser);
//User2 bought all tokens
vm.startPrank(user2);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 1000);
vm.stopPrank();
vm.prank(user1);
systemConfig.updateMarket("Backpack", address(mockPointToken), 0.01 * 1e18, block.timestamp - 1, 3600);
//User will generate tradeTax & salesRevenue from taker ie user2
uint256 userTradeTax =
tokenManager.userTokenBalanceMap(user, address(mockUSDCToken), TokenBalanceType.TaxIncome);
uint256 userSalesRevenue =
tokenManager.userTokenBalanceMap(user, address(mockUSDCToken), TokenBalanceType.SalesRevenue);
uint256 userTotalAmount = userTradeTax + userSalesRevenue;
console2.log("Total amount received by malicious user:", userTotalAmount);
}
Logs:
Amount paid by malicious user: 1000000000000000000000
Total amount received by malicious user: 1030000000000000000000

In the above test we can see, malicious user deposited only 1000e18 tokens as initialCollateral for 1000 points but generated 1030e18 tokens through tradeTax & salesRevenue, which is 3% greater than initialDeposit amount.

Now owner can withdraw these tokens & has no incentive to call settleAskMaker() for buyers & pay them pointsToken. This 3% is just free money for malicious user, also this 3% can go high because this comes from eachTradeTax(which is in malicious user hand)

Impact

Malicious user can rug pull honest user, making then loss for tradeTax & platformFee

Tools Used

Manual Review

Recommendations

There are couple of recommendations:

  1. Require the collateralRate to be 120% or 130% for creating offers initially because owner can create offer with just 10000(100%) collateralRate, this will increase the initial collateral depositAmount from owner

  2. Allow only salesRevenue generated from taker to withdraw but not tradeTax

Updates

Lead Judging Commences

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

finding-PreMarkets-listOffer-collateralRate-manipulate

Valid high severity, because the collateral rate utilized when creating an offer is stale and retrieved from a previously set collateral rate, it allows possible manipilation of refund amounts using an inflated collateral rate to drain funds from the CapitalPool contract

Support

FAQs

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