Security Audit Report
UBet Parlay Audit Report
Reviewed by: 0x52 (@IAm0x52)
Prepared For: UBet
Review Date(s): 12/9/24 - 12/11/24
0x52 Background
As a professional smart contract auditor, I have conducted over 100 security reviews for public and private clients. With 30+ first-place finishes in public contests on platforms like Code4rena and Sherlock, I have been recognized as a top-performing security expert. By prioritizing rigorous analysis and providing actionable recommendations, I have contributed to securing over $1 billion in TVL across 100+ protocols. Throughout my career I have collaborated with many organizations including the prestigious Blackthorn as a founding security researcher and as a Lead Security researcher at SpearbitDAO.
Protocol Summary
UBet Parlay is a smart contract system for batch betting and parlay bets on the UBet platform, supporting multiple markets and event types.
Scope
Repo: ubet-contract-v1
Review Hash: 7b61ff6
Fix Review Hash:
In-Scope Contracts
src/conditions/*src/funding/*src/markets/*src/AdminExecutorAccess.solsrc/Math.solsrc/PackedPrices.sol
Deployment Chain(s)
- Polygon Mainnet
Summary of Findings
| High | Med | Low | |
|---|---|---|---|
| 0 | 0 | 2 | |
| 0 | 0 | 0 |
Low Risk Findings
Events in BatchBet can be spoofed using custom pool/market
Details
for (uint256 i = 0; i < buys.length; i++) {
address market = buys[i].market;
if (!market.supportsInterface(MARKET_MAKER_V1_2_INTERFACE_ID)) {
revert NotAMarket(market);
}
token.safeApprove(market, buys[i].investmentAmount);
(, uint256 feeAmount,) = IMarketMakerV1_2(market).buyFor(
buyer,
buys[i].investmentAmount,
buys[i].outcomeIndex,
buys[i].minOutcomeTokensToBuy,
extraFeeDecimal,
feeProfileId
);
totalFees += feeAmount;
}
if (totalInvestment > 0 && (data.length > 0 || affiliate != address(0x0))) {
emit BuyWithData(buyer, affiliate, address(token), totalInvestment, data);
}Above we see that the market interacted with can be arbitrarily supplied by the user. This allows specifying a contract different from a UBet owned market. Along with this take note that the market address is never specified inside the BuyWithData event. The result is that events can be spoofed to make user/affiliate volume look significantly higher than the real value. This can have a negative effect on any offchain analytics or calculations.
Lines of Code
Recommendation
Include the market address in event so that non-UBet market data can be filtered out
Remediation
Transferring ERC1155 tokens before applying parent return ops allowing reentrancy
Details
conditionalTokens.safeTransferFrom(address(this), receiver, positionId(outcomeIndex), outcomeTokensBought, "");
// Last index outcome is the refund outcome. Give back the same amount of tokens as collateral invested, including fees
conditionalTokens.safeTransferFrom(address(this), receiver, positionId(refundIndex), investmentAmount, "");
// Return collateral back to parent once everything is settled with the buyer
_applyParentReturn(parentOps);Above we see that inside the buyFor function the conditionalTokens (ERC1155) are transferred to the buyer prior to the the collateral being returned to the parent pool. This enables reentrancy prior to a significant state change to the market funding pool. Although no significant attack vectors were discovered as a result of this reentrancy is it still recommended to resolve this issue it may cause vulnerabilities in future versions.
Lines of Code
Recommendation
Move token transfers to after _applyParentReturn. Additionally utilize safeBatchTransferFrom to prevent reentrancy between conditional token transfers.