Summary
In the PreMarkets::createOffer()
function, the offerId
stored in the offerInfoMap
and stockInfoMap
does not match the actually generated offerAddr
and stockAddr
.
Vulnerability Details
In the PreMarkets::createOffer()
function, offerId
is incremented after generating makerAddr
, offerAddr
, and stockAddr
, which causes the offerId stored in offerInfoMap
and stockInfoMap
to not match the actually generated offerAddr and stockAddr. Since offerId
is incremented after generating these addresses, the offerId
when updating the storage is 1 greater than the offerId
used for the actual generated address.
function createOffer(CreateOfferParams calldata params) external payable {
address makerAddr = GenerateAddress.generateMakerAddress(offerId);
@> address offerAddr = GenerateAddress.generateOfferAddress(offerId);
@> address stockAddr = GenerateAddress.generateStockAddress(offerId);
@> offerId = offerId + 1;
offerInfoMap[offerAddr] = OfferInfo({
@> id: offerId,
authority: _msgSender(),
maker: makerAddr,
offerStatus: OfferStatus.Virgin,
offerType: params.offerType,
points: params.points,
amount: params.amount,
collateralRate: params.collateralRate,
abortOfferStatus: AbortOfferStatus.Initialized,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});
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
});
emit CreateOffer(
offerAddr,
makerAddr,
stockAddr,
params.marketPlace,
_msgSender(),
params.points,
params.amount
);
}
}
And id
is not the only mark in the comment
* @title StockInfo
* @dev Struct of StockInfo
* @notice id, stockStatus, stockType, authority, maker, preOffer, points, amount, offer
@> * @param id the unique id of stock.
* @param stockStatus the status of stock, detail in OfferStatus.sol.
* @param stockType the type of stock, detail in OfferStatus.sol.
* @param authority the owner of stock.
* @param maker the maker of stock, is a virtual address, storage as MakerInfo.
* @param preOffer the preOffer of stock.
* @param points the points of sell or buy stock.
* @param amount receive or used collateral amount when sell or buy.
* @param offer the offer of stock, is a virtual address, storage as OfferInfo.
*/
struct StockInfo {
uint256 id;
StockStatus stockStatus;
StockType stockType;
address authority;
address maker;
address preOffer;
uint256 points;
uint256 amount;
address offer;
}
Poc
Please add the test code to test/PreMarkets.t.sol
and execute
function testID_is_not_unique() public {
vm.startPrank(user);
assert(preMarktes.offerId() == 0);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address userStockAddr = GenerateAddress.generateStockAddress(0);
address userOfferAddr = GenerateAddress.generateOfferAddress(0);
assert(preMarktes.offerId() == 1);
preMarktes.createTaker(userOfferAddr, 500);
address userCreateTakerStockAddr = GenerateAddress.generateStockAddress(1);
assert(userCreateTakerStockAddr != userStockAddr);
assertEq(preMarktes.getStockInfo(userStockAddr).id,preMarktes.getStockInfo(userCreateTakerStockAddr).id);
console2.log("preMarktes.getStockInfo(userStockAddr).id:",preMarktes.getStockInfo(userStockAddr).id);
console2.log("preMarktes.getStockInfo(userCreateTakerStockAddr).id:",preMarktes.getStockInfo(userCreateTakerStockAddr).id);
vm.stopPrank();
}
Code Snippet
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L39-L157
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L295-L396
Impact
In the PreMarkets::createOffer()
function, the offerId
stored in the offerInfoMap
and stockInfoMap
does not match the actually generated offerAddr
and stockAddr
.
Tools Used
Manual Review
Recommendations
Consider moving the increment of offerId
to the end of the function, or cache offerId
in currentOfferId
for use.
function createOffer(CreateOfferParams calldata params) external payable {
// SNIP..
/// @dev generate address for maker, offer, stock.
address makerAddr = GenerateAddress.generateMakerAddress(offerId);
address offerAddr = GenerateAddress.generateOfferAddress(offerId);
address stockAddr = GenerateAddress.generateStockAddress(offerId);
// SNIP..
- offerId = offerId + 1;
// SNIP...
/// @dev update offer info
offerInfoMap[offerAddr] = OfferInfo({
id: offerId,
authority: _msgSender(),
maker: makerAddr,
offerStatus: OfferStatus.Virgin,
offerType: params.offerType,
points: params.points,
amount: params.amount,
collateralRate: params.collateralRate,
abortOfferStatus: AbortOfferStatus.Initialized,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});
/// @dev update stock info
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
});
+ offerId = offerId + 1;
emit CreateOffer(
offerAddr,
makerAddr,
stockAddr,
params.marketPlace,
_msgSender(),
params.points,
params.amount
);
}
}