Summary
Different stocks/offers can be created with the same id.
Vulnerability Details
According to NatSpec of struct StockInfo, id
should be the unique id of stock.
@param id the unique id of stock.
However, different stocks can be created with the same id.
When an offer is created, offerId
is incremented by and is assigned to the stock info which is created along with the offer. At this time, offer and stock have the same id
.
stockInfoMap[stockAddr] = StockInfo({
@> id: offerId,
stockStatus: StockStatus.Initialized,
stockType: params.offerType == OfferType.Ask
? StockType.Bid
: StockType.Ask,
authority: _msgSender(),
maker: makerAddr,
preOffer: address(0x0),
offer: offerAddr,
points: params.points,
amount: params.amount
});
The stock can be filled and a sub stock will be created, then `offerId` is incremented by .
stockInfoMap[stockAddr] = StockInfo({
@> id: offerId,
stockStatus: StockStatus.Initialized,
stockType: offerInfo.offerType == OfferType.Ask
? StockType.Bid
: StockType.Ask,
authority: _msgSender(),
maker: offerInfo.maker,
preOffer: _offer,
points: _points,
amount: depositAmount,
offer: address(0x0)
});
@> offerId = offerId + 1;
This means the origin stock and the sub stock shares the same id
, and this breaks the promise that id
shoud be unique across different stocks.
Likewise, when user list the sub stock, a sub offer is created, and this sub offer will use the same id
as the sub stock. This means the origin offer and the sub offer have the same id
.
offerInfoMap[offerAddr] = OfferInfo({
@> id: stockInfo.id,
authority: _msgSender(),
maker: offerInfo.maker,
offerStatus: OfferStatus.Virgin,
offerType: offerInfo.offerType,
abortOfferStatus: AbortOfferStatus.Initialized,
points: stockInfo.points,
amount: _amount,
collateralRate: _collateralRate,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});
Please run the PoC in PreMarkets.t.sol to verify:
function testAudit_OfferId() public {
address auditMarketPlace = GenerateAddress.generateMarketPlaceAddress("AuditMarket");
vm.startPrank(user1);
systemConfig.createMarketPlace("AuditMarket", false);
systemConfig.updateMarket("AuditMarket", address(mockPointToken), 0.5e18, block.timestamp + 30 days, 2 days);
vm.stopPrank();
address alice = makeAddr("Alice");
deal(address(mockUSDCToken), alice, 1200e18);
address bob = makeAddr("Bob");
deal(address(mockUSDCToken), bob, 1005e18);
address aliceStockAddr = GenerateAddress.generateStockAddress(preMarktes.offerId());
address aliceOfferAddr = GenerateAddress.generateOfferAddress(preMarktes.offerId());
vm.startPrank(alice);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createOffer(CreateOfferParams({
marketPlace: auditMarketPlace,
tokenAddress: address(mockUSDCToken),
points: 1000,
amount: 1000e18,
collateralRate: 12000,
eachTradeTax: 0,
offerType: OfferType.Ask,
offerSettleType: OfferSettleType.Turbo
}));
vm.stopPrank();
address bobStockAddr = GenerateAddress.generateStockAddress(preMarktes.offerId());
address bobOfferAddr = GenerateAddress.generateOfferAddress(preMarktes.offerId());
vm.startPrank(bob);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(aliceOfferAddr, 1000);
preMarktes.listOffer(bobStockAddr, 100e18, 12000);
vm.stopPrank();
StockInfo memory aliceStockInfo = preMarktes.getStockInfo(aliceStockAddr);
StockInfo memory bobStockInfo = preMarktes.getStockInfo(bobStockAddr);
assertEq(aliceStockInfo.id, bobStockInfo.id);
OfferInfo memory aliceOfferInfo = preMarktes.getOfferInfo(aliceOfferAddr);
OfferInfo memory bobOfferInfo = preMarktes.getOfferInfo(bobOfferAddr);
assertEq(aliceOfferInfo.id, bobOfferInfo.id);
}
Impact
Different stocks/offers have the same id.
Tools Used
Manual Review
Recommendations
When create offer, assign offerId
to stock/offer before incrementing offerId
by .