Summary
When the DeliveryPlace::settleAskMaker() function calls tokenManager.addTokenBalance() to update the user balance, the TokenBalanceType parameter uses an operation, resulting in a balance update error
Vulnerability Details
The source code snippet of the DeliveryPlace::settleAskMaker() function is as follows. As marked by @>, TokenBalanceType is used incorrectly.
function settleAskMaker(address _offer, uint256 _settledPoints) external {
@> uint256 makerRefundAmount;
if (_settledPoints == offerInfo.usedPoints) {
if (offerInfo.offerStatus == OfferStatus.Virgin) {
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
offerInfo.amount,
true,
Math.Rounding.Floor
);
} else {
uint256 usedAmount = offerInfo.amount.mulDiv(
offerInfo.usedPoints,
offerInfo.points,
Math.Rounding.Floor
);
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
usedAmount,
true,
Math.Rounding.Floor
);
}
tokenManager.addTokenBalance(
@> TokenBalanceType.SalesRevenue,
_msgSender(),
makerInfo.tokenAddress,
@> makerRefundAmount
);
}
}
Poc
To demonstrate the issue, add the following test code to test/PreMarkets.t.sol and run it:
function testAskPointAndAmount() public {
vm.prank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.prank(user2);
preMarktes.createTaker(offerAddr, 1000);
address user2StockAddr = GenerateAddress.generateStockAddress(1);
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, 1000);
vm.stopPrank();
vm.prank(user2);
deliveryPlace.closeBidTaker(user2StockAddr);
console2.log("--------------- user balance ---------------");
balanceHelper(user);
}
function balanceHelper(address _user) view public {
uint256 usermockUSDCTokenAmount_TaxIncome = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.TaxIncome
);
console2.log("usermockUSDCTokenAmount_TaxIncome:",usermockUSDCTokenAmount_TaxIncome);
uint256 usermockUSDCTokenAmount_ReferralBonus = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.ReferralBonus
);
console2.log("usermockUSDCTokenAmount_ReferralBonus:",usermockUSDCTokenAmount_ReferralBonus);
uint256 usermockUSDCTokenAmount_SalesRevenue = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.SalesRevenue
);
console2.log("usermockUSDCTokenAmount_SalesRevenue:",usermockUSDCTokenAmount_SalesRevenue);
uint256 usermockUSDCTokenAmount_RemainingCash = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.RemainingCash
);
console2.log("usermockUSDCTokenAmount_RemainingCash:",usermockUSDCTokenAmount_RemainingCash);
uint256 usermockUSDCTokenAmount_MakerRefund = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.MakerRefund
);
console2.log("usermockUSDCTokenAmount_MakerRefund:",usermockUSDCTokenAmount_MakerRefund);
uint256 usermockUSDCTokenAmount_PointToken = tokenManager.userTokenBalanceMap(
address(_user),
address(mockUSDCToken),
TokenBalanceType.PointToken
);
console2.log("usermockUSDCTokenAmount_PointToken:",usermockUSDCTokenAmount_PointToken);
}
From the following output, we can see that userTokenBalanceMap[user][address(mockUSDCToken)][TokenBalanceType.SalesRevenue] == 22000000000000000(2.2e16), while the actual values should be userTokenBalanceMap[user][address(mockUSDCToken)][TokenBalanceType.SalesRevenue] == 1e16 and userTokenBalanceMap[user][address(mockUSDCToken)][TokenBalanceType.MakerRefund] == 1.2e16
[PASS] testAskPointAndAmount() (gas: 1172769)
Logs:
--------------- user balance ---------------
usermockUSDCTokenAmount_TaxIncome: 300000000000000
usermockUSDCTokenAmount_ReferralBonus: 0
@> usermockUSDCTokenAmount_SalesRevenue: 22000000000000000
usermockUSDCTokenAmount_RemainingCash: 0
@> usermockUSDCTokenAmount_MakerRefund: 0
usermockUSDCTokenAmount_PointToken: 0
Code Snippet
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/DeliveryPlace.sol#L222-L325
Impact
The source code snippet of the DeliveryPlace::settleAskMaker() function is as follows. As marked by @>, TokenBalanceType is used incorrectly.
Tools Used
Manual Review
Recommendations
tokenManager.addTokenBalance(
+ TokenBalanceType.MakerRefund,
- TokenBalanceType.SalesRevenue,
_msgSender(),
makerInfo.tokenAddress,
makerRefundAmount
);
test again:
[PASS] testAskPointAndAmount() (gas: 1224694)
Logs:
--------------- user balance ---------------
usermockUSDCTokenAmount_TaxIncome: 300000000000000
usermockUSDCTokenAmount_ReferralBonus: 0
@> usermockUSDCTokenAmount_SalesRevenue: 10000000000000000
usermockUSDCTokenAmount_RemainingCash: 0
@> usermockUSDCTokenAmount_MakerRefund: 12000000000000000
usermockUSDCTokenAmount_PointToken: 0