Part of CertiK’s retainer series
CertiK reviewed WEMIX (Wemix Retainer 123 PlayNftMarket) on 16 Dec 2024: 20 findings, 0 critical; 7 marked fixed, 13 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 0 | 2 |
| Medium | 1 | 0 | 3 |
| Low | 10 | 0 | 2 |
The `cancel()` function in the `PlayMarketOfferBiddingOrder` contract is intended to be called by the market to allow users to cancel their orders. However, this function is missing the `onlyMarket` access control modifier. This vulnerability allows malicious actors to directly call the `cancel()` function in the `PlayMarketOfferBiddingOrder` contract, enabling them to cancel orders on behalf of other users without their permission.
In `offerSellOrder()` function, the meaning of the `amount` parameter varying based on `order.orderStrategyType`: - For `order.orderStrategyType == Type.OrderStrategyType.SELL`, buyers need to pay `orderPrice = _order.price * amount`, where `amount` represents the number of NFTs to purchase (`PlayMarketFixedPriceOrder#L52`). - For `order.orderStrategyType == Type.OrderStrategyType.OFFER_BIDDING`, buyers need to pay `amount` of payment tokens, where `amount` represents the order price. To ensure sufficient payment in the second case, the function performs the following check: ```solidity=277 require(amount >= _order.price, "MB: insufficient amount" ); ``` However, this check breaks the first case where `amount` represents the number of NFTs, resulting in buyers not being able to call the `offerSellOrder()` function to buy NFTs if `order.orderStrategyType == Type.OrderStrategyType.SELL`. Additionally, for `OFFER_BIDDING` orders, buyers should be allowed to increase their offered price by any amount, rather than incrementing by `order.price`.
The `PlayMarketFixedPriceOrderV2` contract has been updated to inherit from `PlayMarketOrderBaseV3`, which mandates using WWEMIX as the payment token for non-ERC20 payment orders. In the `offer` function of `PlayMarketFixedPriceOrderV2`, the contract checks if the payment token is `address(0)`, implying a transfer of Ether: ```solidity=53 if (_order.payment == address(0)) { require(msg.value == orderPrice, "FP : Invalid value"); } else { IERC20(_order.payment).safeTransferFrom(payer, address(this), orderPrice); } ``` The check does not appropriately handle WWEMIX, as WWEMIX is a wrapped version of WEMIX and should not be recognized as `address(0)`. Furthermore, the internal functions `_nftOffer()` and `_bundleOffer()` fail to unwrap WWEMIX before invoking `_transferToken()`. Consequently, orders with WWEMIX payment cannot be processed correctly, leading to payment failures.
In the process of wrapping WEMIX tokens to WWEMIX tokens, the `Checker::checkBalance()` function attempts to verify the strategy contract's token balance. This function checks the balance for a specified token in order to ensure the contract received the correct amount. As shown in the code snippet below, if the token is denoted by `address(0)`, the function checks the ETH balance of the contract using `address(this).balance`: ```solidity=59 function checkBalance(address token, uint value) internal view returns (bool) { uint balance; if (token == address(0)) { balance = address(this).balance; } else { balance = IERC20(token).balanceOf(address(this)); } return balance >= value; } ``` However, since the strategy contract receives WWEMIX tokens rather than the native ETH in the new commit a4c547d, checking the balance of `address(this).balance` when the payment token is `address(0)` will not reflect the WWEMIX token balance, leading to a transaction revert.
Users can cancel their own unaccepted offers to refund their payments. However, this can unintentionally lead to the cancellation of an accepted offer in the following scenario: 1. A seller accepts an offer, updating the `order.orderStatus` to `ACCEPTED`. 2. The owner of the accepted offer does not claim the order immediately, leaving the order status as `ACCEPTED`. 3. Another user cancels their own unaccepted offer, triggering the following code snippet: ```solidity=250 if (_order.orderStatus == Type.OrderStatus.ACCEPTED) { _cancelAccept(_order); } ``` This logic unintentionally cancels the accepted offer when it should only affect the unaccepted offer being cancelled.
If `priceOracle` is not a zero address, the `offerBuyOrder()` function will store NFT price of the completed order. However, the `claimOrder()` function, which marks the `OFFER_BIDDING` order as completed, does not save the NFT price to the `priceOracle` contract.
The `_registeredOnOfferBidding` mapping tracks `OFFER_BIDDING` orders and prevents the registration of other orders for the same NFT when it is registered for an `OFFER_BIDDING` order. However, there are inconsistent issues regarding the usage of this mapping: - The mapping also tracks `SELL` and `AUCTION` orders. - Provide offer for `SELL` orders will not remove the order ID from `_registeredOnOfferBidding` mapping. - Claim order for `AUCTION` orders will not remove the order ID from `_registeredOnOfferBidding` mapping.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (7)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us