Summary
DeliveryPlace::settleAskTaker has an incorrect authorization check that allows the maker to settle the taker's ask instead of the taker themselves. It also incorrectly requires makers to deposit tokens to settle takers' orders.
DeliveryPlace.sol#L361
function settleAskTaker(address _stock, uint256 _settledPoints) external {
if (status == MarketPlaceStatus.AskSettling) {
@> if (_msgSender() != offerInfo.authority) {
revert Errors.Unauthorized();
}
}else {
}
uint256 settledPointTokenAmount = marketPlaceInfo.tokenPerPoint * _settledPoints;
ITokenManager tokenManager = tadleFactory.getTokenManager();
if (settledPointTokenAmount > 0) {
@> tokenManager.tillIn(_msgSender(), marketPlaceInfo.tokenAddress, settledPointTokenAmount, true);
}
}
Impact
This vulnerability can lead to unintended financial losses for makers as they are forced to deposit their own tokens to settle orders that should be settled by takers.
Proof of Concept
Place the following test in PreMarkets.t.sol:
function test_makerDepositsInsteadOfTaker_issue() public {
address alice = makeAddr("alice");
address bob = makeAddr("bob");
uint256 INITIAL_BALANCE = 1000 * 1e18;
uint256 MAKER_POINTS = 10_000;
uint256 TAKER_POINTS = 5_000;
deal(address(mockUSDCToken), alice, INITIAL_BALANCE);
deal(address(mockUSDCToken), bob, INITIAL_BALANCE);
vm.startPrank(alice);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createMaker(
CreateMakerParams(
marketPlace,
address(mockUSDCToken),
MAKER_POINTS,
0.01 * 1e18,
10_000 * 1.2,
10_000 * 0.05,
OfferType.Bid,
OfferSettleType.Turbo
)
);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
vm.startPrank(bob);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offerAddr, TAKER_POINTS);
address stockAddr = GenerateAddress.generateStockAddress(1);
vm.stopPrank();
vm.startPrank(user1);
systemConfig.updateMarket("Backpack", address(mockUSDCToken), 0.01 * 1e18, block.timestamp - 1, 3600);
vm.stopPrank();
vm.startPrank(alice);
uint256 aliceBalanceBeforeSettlement = mockUSDCToken.balanceOf(alice);
uint256 bobBalanceBeforeSettlement = mockUSDCToken.balanceOf(bob);
deliveryPlace.settleAskTaker(stockAddr, TAKER_POINTS);
uint256 aliceBalanceAfterSettlement = mockUSDCToken.balanceOf(alice);
uint256 bobBalanceAfterSettlement = mockUSDCToken.balanceOf(bob);
console.log("> Alice's balance BEFORE settleAskTaker: %18e", aliceBalanceBeforeSettlement);
console.log("> Bob's balance BEFORE settleAskTaker: %18e\n", bobBalanceBeforeSettlement);
console.log(">> Alice's balance AFTER settleAskTaker: %18e", aliceBalanceAfterSettlement);
console.log(">> Bob's balance AFTER settleAskTaker: %18e\n", bobBalanceAfterSettlement);
assertEq(
aliceBalanceBeforeSettlement,
aliceBalanceAfterSettlement,
"Alice's balance should not change, indicating incorrect token flow."
);
}
The aforementioned test should yield the following output:
Ran 1 test for test/PreMarkets.t.sol:PreMarketsTest
[FAIL. Reason: assertion failed] test_makerDepositsInsteadOfTaker_issue() (gas: 1293294)
Logs:
> Alice's balance BEFORE settleAskTaker: 999.99
> Bob's balance BEFORE settleAskTaker: 999.993725
>> Alice's balance AFTER settleAskTaker: 949.99
>> Bob's balance AFTER settleAskTaker: 999.993725
Error: Alice's balance should not change, indicating incorrect token flow.
Error: a == b not satisfied [uint]
Expected: 949990000000000000000
Actual: 999990000000000000000
Recommended Mitigation Steps
Correct the authorization check to ensure that only the taker can call settleAskTaker:
function settleAskTaker(address _stock, uint256 _settledPoints) external {
// previous code...
if (status == MarketPlaceStatus.AskSettling) {
- if (_msgSender() != offerInfo.authority) {
+ if (_msgSender() != stockInfo.authority) {
revert Errors.Unauthorized();
}
}else{
// the rest of the function...
}
}
Now we can update the previous test to allow Bob to settle his order:
function test_makerDepositsInsteadOfTaker_fixed() public {
address alice = makeAddr("alice");
address bob = makeAddr("bob");
uint256 INITIAL_BALANCE = 1000 * 1e18;
uint256 MAKER_POINTS = 10_000;
uint256 TAKER_POINTS = 5_000;
deal(address(mockUSDCToken), alice, INITIAL_BALANCE);
deal(address(mockUSDCToken), bob, INITIAL_BALANCE);
vm.startPrank(alice);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createMaker(
CreateMakerParams(
marketPlace,
address(mockUSDCToken),
MAKER_POINTS,
0.01 * 1e18,
10_000 * 1.2,
10_000 * 0.05,
OfferType.Bid,
OfferSettleType.Turbo
)
);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
vm.startPrank(bob);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offerAddr, TAKER_POINTS);
address stockAddr = GenerateAddress.generateStockAddress(1);
vm.stopPrank();
vm.startPrank(user1);
systemConfig.updateMarket("Backpack", address(mockUSDCToken), 0.01 * 1e18, block.timestamp - 1, 3600);
vm.stopPrank();
vm.startPrank(bob);
uint256 aliceBalanceBeforeSettlement = mockUSDCToken.balanceOf(alice);
uint256 bobBalanceBeforeSettlement = mockUSDCToken.balanceOf(bob);
deliveryPlace.settleAskTaker(stockAddr, TAKER_POINTS);
uint256 aliceBalanceAfterSettlement = mockUSDCToken.balanceOf(alice);
uint256 bobBalanceAfterSettlement = mockUSDCToken.balanceOf(bob);
console.log("> Alice's balance BEFORE settleAskTaker: %18e", aliceBalanceBeforeSettlement);
console.log("> Bob's balance BEFORE settleAskTaker: %18e\n", bobBalanceBeforeSettlement);
console.log(">> Alice's balance AFTER settleAskTaker: %18e", aliceBalanceAfterSettlement);
console.log(">> Bob's balance AFTER settleAskTaker: %18e\n", bobBalanceAfterSettlement);
assertEq(
aliceBalanceBeforeSettlement,
aliceBalanceAfterSettlement,
"Alice's balance should not change, indicating incorrect token flow."
);
}
The updated test should yield the following output:
[PASS] test_makerDepositsInsteadOfTaker_fixed() (gas: 1278601)
Logs:
> Alice's balance BEFORE settleAskTaker: 999.99
> Bob's balance BEFORE settleAskTaker: 999.993725
>> Alice's balance AFTER settleAskTaker: 999.99
>> Bob's balance AFTER settleAskTaker: 949.993725