Links
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L83
Summary
There is a logical issue in PreMarkets::createOffer function. The function updated the offerId variable before updating the makerInfoMap, offerInfoMap and stockInfoMap mappings.
Vulnerability Details
The PreMarkets::createOffer is responsible for creating new offers. It involves generating unique addresses for the maker, offer, and stock based on the offerId:
function createOffer(CreateOfferParams calldata params) external payable {
        
         * @dev points and amount must be greater than 0
         * @dev eachTradeTax must be less than 100%, decimal scaler is 10000
         * @dev collateralRate must be more than 100%, decimal scaler is 10000
         */
        if (params.points == 0x0 || params.amount == 0x0) {
            revert Errors.AmountIsZero();
        }
        if (params.eachTradeTax > Constants.EACH_TRADE_TAX_DECIMAL_SCALER) {
            revert InvalidEachTradeTaxRate();
        }
        if (params.collateralRate < Constants.COLLATERAL_RATE_DECIMAL_SCALER) {
            revert InvalidCollateralRate();
        }
        
        ISystemConfig systemConfig = tadleFactory.getSystemConfig();
        MarketPlaceInfo memory marketPlaceInfo = systemConfig
            .getMarketPlaceInfo(params.marketPlace);
        marketPlaceInfo.checkMarketPlaceStatus(
            block.timestamp,
            MarketPlaceStatus.Online
        );
        
        address makerAddr = GenerateAddress.generateMakerAddress(offerId);
        address offerAddr = GenerateAddress.generateOfferAddress(offerId);
        address stockAddr = GenerateAddress.generateStockAddress(offerId);
        if (makerInfoMap[makerAddr].authority != address(0x0)) {
            revert MakerAlreadyExist();
        }
        if (offerInfoMap[offerAddr].authority != address(0x0)) {
            revert OfferAlreadyExist();
        }
        if (stockInfoMap[stockAddr].authority != address(0x0)) {
            revert StockAlreadyExist();
        }
@>      offerId = offerId + 1;
        {
            
            uint256 transferAmount = OfferLibraries.getDepositAmount(
                params.offerType,
                params.collateralRate,
                params.amount,
                true,
                Math.Rounding.Ceil
            );
            ITokenManager tokenManager = tadleFactory.getTokenManager();
            tokenManager.tillIn{value: msg.value}(
                _msgSender(),
                params.tokenAddress,
                transferAmount,
                false
            );
        }
        
        makerInfoMap[makerAddr] = MakerInfo({
            offerSettleType: params.offerSettleType,
            authority: _msgSender(),
            marketPlace: params.marketPlace,
            tokenAddress: params.tokenAddress,
            originOffer: offerAddr,
            platformFee: 0,
            eachTradeTax: params.eachTradeTax
        });
        
        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
        );
    }
The problem is that the function increments the offerId variable before updating the mappings (makerInfoMap, offerInfoMap and stockInfoMap). That means the id in the mappings will be different than the one used for generating the makerAddr, offerAddr and stockAddr.
Impact
The use of the incorrect id in the mappings leads to inconsistencies where the generated addresses (makerAddr, offerAddr, stockAddr) do not correspond to the correct offerId.
Also, if the createTaker function is called immediately after the createOffer function, the generated address stockAddr and the stockInfoMap in createTaker will use the same offerId that corresponds to the updating mappings in the PreMarkets::createOffer function and that is incorrect.
The createTaker function increments correctly the offerId after updating the mapping, but the createOffer function doesn't update it in the correct place.
Tools Used
Manual Review
Recommendations
Update the offerId after updating the mappings to ensure that the offerId for the generated addresses is the same as the one used in the updating the mappings:
function createOffer(CreateOfferParams calldata params) external payable {
       .
       .
       .
        if (stockInfoMap[stockAddr].authority != address(0x0)) {
            revert StockAlreadyExist();
        }
-       offerId = offerId + 1;
        {
            /// @dev transfer collateral from _msgSender() to capital pool
            uint256 transferAmount = OfferLibraries.getDepositAmount(
                params.offerType,
                params.collateralRate,
                params.amount,
                true,
                Math.Rounding.Ceil
            );
            ITokenManager tokenManager = tadleFactory.getTokenManager();
            tokenManager.tillIn{value: msg.value}(
                _msgSender(),
                params.tokenAddress,
                transferAmount,
                false
            );
        }
        /// @dev update maker info
        makerInfoMap[makerAddr] = MakerInfo({
            offerSettleType: params.offerSettleType,
            authority: _msgSender(),
            marketPlace: params.marketPlace,
            tokenAddress: params.tokenAddress,
            originOffer: offerAddr,
            platformFee: 0,
            eachTradeTax: params.eachTradeTax
        });
        /// @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
        );
    }