Summary
PreMarkets::listOffer
uses the preOffers collateralRate
instead of the provided one(_collateralRate
) to calculate the amount of collateral required for a protected offer ,due to this , an attacker can provide an absurdly large collateral rate that is then used to calculate the refund amount when they cancel/settle/abort their offer. Effectively, they can drain the contract by providing just the right value as their collateral rate
Vulnerability Details
From the code snippet here where the transferAmount
is calculated
if (makerInfo.offerSettleType == OfferSettleType.Protected) {
uint256 transferAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
-> offerInfo.collateralRate,
_amount,
true,
Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.tillIn{value: msg.value}(
_msgSender(),
makerInfo.tokenAddress,
transferAmount,
false
);
}
The transferAmount
deposited by the sender is calculated using the collateralRate
of the stockInfo.preOffer
not the collateralRate
of the offer itself about to be created.
However ,when the offer is closed/settled/aborted , the transferAmount
is calculated using the collateralRate
of the offer itself. One such case is in PreMarkets::closeOffer
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L433-L456
* @dev update refund token from capital pool to balance
* @dev offer settle type is protected or original offer
*/
if (
makerInfo.offerSettleType == OfferSettleType.Protected ||
stockInfo.preOffer == address(0x0)
) {
-> uint256 refundAmount = OfferLibraries.getRefundAmount(
offerInfo.offerType,
offerInfo.amount,
offerInfo.points,
offerInfo.usedPoints,
offerInfo.collateralRate
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.addTokenBalance(
TokenBalanceType.MakerRefund,
_msgSender(),
makerInfo.tokenAddress,
refundAmount
);
}
When offer type is Ask , getRefundAmount
calculates the refund amount as (amount - usedAmount)*collateralRate,
Impact
HIGH/CRITICAL - Severe loss of funds
Tools Used
Manual Review
Recommendations
function listOffer(
address _stock,
uint256 _amount,
uint256 _collateralRate
) external payable {
//...ommited for brevity
/// @dev change abort offer status when offer settle type is turbo
if (makerInfo.offerSettleType == OfferSettleType.Turbo) {
address originOffer = makerInfo.originOffer;
OfferInfo memory originOfferInfo = offerInfoMap[originOffer];
if (_collateralRate != originOfferInfo.collateralRate) {
revert InvalidCollateralRate();
}
originOfferInfo.abortOfferStatus = AbortOfferStatus.SubOfferListed;
}
/// @dev transfer collateral when offer settle type is protected
if (makerInfo.offerSettleType == OfferSettleType.Protected) {
uint256 transferAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
- offerInfo.collateralRate,
+ _collateralRate,
_amount,
true,
Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.tillIn{value: msg.value}(
_msgSender(),
makerInfo.tokenAddress,
transferAmount,
false
);
}
address offerAddr = GenerateAddress.generateOfferAddress(stockInfo.id);
if (offerInfoMap[offerAddr].authority != address(0x0)) {
revert OfferAlreadyExist();
}
//...ommited function body for brevity as it is irrelevant
}