Summary
Bid takers must call DeliveryPlace::closeBidTaker to receive the points purchased in the pre-market. The addTokenBalance hook in the TokenManager contract is responsible for transferring the points settled by the ask maker. However, during this process, the address of the point token is incorrectly set, and the quoted token from the offer made by the ask maker is used instead.
Vulnerability Details
As a result, the bid taker does not receive the intended points but instead receives an equivalent amount in the quoted token of the offer.
uint256 pointTokenAmount = offerInfo.settledPointTokenAmount.mulDiv(
userRemainingPoints,
offerInfo.usedPoints,
Math.Rounding.Floor
);
tokenManager.addTokenBalance(
TokenBalanceType.PointToken,
_msgSender(),
@> makerInfo.tokenAddress,
pointTokenAmount
);
Impact
See PoC below:
function test_ask_offer_turbo_eth() public {
vm.startPrank(user);
preMarktes.createOffer{value: 0.012 * 1e18}(
CreateOfferParams(
marketPlace,
address(weth9),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker{value: 0.005175 * 1e18}(offerAddr, 500);
address stock1Addr = GenerateAddress.generateStockAddress(1);
preMarktes.listOffer(stock1Addr, 0.006 * 1e18, 12000);
address offer1Addr = GenerateAddress.generateOfferAddress(1);
preMarktes.closeOffer(stock1Addr, offer1Addr);
preMarktes.relistOffer(stock1Addr, offer1Addr);
vm.stopPrank();
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.settleAskMaker(offerAddr, 500);
deliveryPlace.closeBidTaker(stock1Addr);
vm.stopPrank();
uint256 balanceUserTokenPoints = tokenManager.userTokenBalanceMap(
user,
address(mockPointToken),
TokenBalanceType.PointToken
);
console2.log(balanceUserTokenPoints);
assertEq(balanceUserTokenPoints, 0);
uint256 balanceUserWETH = tokenManager.userTokenBalanceMap(
user,
address(weth9),
TokenBalanceType.PointToken
);
console2.log(balanceUserWETH);
assertEq(balanceUserWETH, 5e18);
}
Tools Used
Manual review.
Recommendations
Load marketplaceInfo struct and use token address set in marketplace when calling the hook:
...
(
OfferInfo memory preOfferInfo,
MakerInfo memory makerInfo,
MarketPlaceInfo memory marketPlaceInfo,
) = getOfferInfo(stockInfo.preOffer);
...
tokenManager.addTokenBalance(
TokenBalanceType.PointToken,
_msgSender(),
marketPlaceInfo.tokenAddress,
pointTokenAmount
);