📅 Audit Timeline
Score adjusted from 8.5 → 9.0 after team clarifications resolved L-02 (false positive) and confirmed intentional design decisions
📋 Table of Contents
1. Executive Summary
Contract Audited: BasePaintMarket v1.11
Blockchain: Base L2 (Chain ID: 8453)
Solidity Version: ^0.8.24
Pattern: UUPS Upgradeable Proxy (OpenZeppelin v5.x)
The BasePaintMarket contract is a well-designed NFT marketplace for trading complete BasePaint year sets (365 NFTs per bundle). Following team clarifications, this audit confirms the contract demonstrates excellent security practices with thoughtful architectural decisions.
Final Assessment:
- 0 Critical / 0 High vulnerabilities
- 0 Medium issues requiring fixes (both acknowledged as intentional design)
- 1 False Positive identified (L-02: eip712Domain() already exists)
- All findings addressed through design documentation or acknowledgment
- Final Verdict: Production Ready
2. Contract Overview
Architecture
| Component | Description |
|---|---|
| Listing System | Approval-based (non-escrow). NFTs remain with seller until purchase. |
| Offer System | Off-chain storage with backend validation. On-chain signature replay prevention only. |
| Bundle Types | YEAR_1 (Days 1-365) and YEAR_2 (Days 366-730), each containing 365 NFTs |
| Payment | ETH for listings, WETH for collection offers |
| Supply Context | ~155 Year 1 sets, ~103 Year 2 sets (limited supply marketplace) |
3. Findings Summary
| ID | Title | Severity | Resolution |
|---|---|---|---|
| M-01 | ETH Received During Buy Can Get Stuck if Seller Reverts | Medium | Won't Fix - By Design |
| M-02 | No Minimum Price Validation for Collection Offers | Medium | Won't Fix - Backend Mitigated |
| L-01 | getActiveListings Has O(n) Complexity | Low | Acknowledged |
| L-02 | No View Function for EIP-712 Domain Separator | Low | False Positive |
| L-03 | Owner Can Set minListingPrice to Zero | Low | Acknowledged - Intentional |
| I-01 | Storage Gap Documentation | Info | Noted |
| I-02 | Consider Batch Listing Creation | Info | Won't Implement |
| I-03 | Missing Events for Some State Changes | Info | Noted - Already Covered |
| I-04 | Contract Does Not Implement ERC1155Receiver | Info | Correct - By Design |
4. Detailed Findings with Team Responses
Medium Severity
[M-01] ETH Received During Buy Can Get Stuck if Seller Reverts
Won't Fix - By DesignLocation: _distributeFunds() and buyListing()
Original Finding:
If the seller's address is a contract that reverts on receiving ETH, the entire transaction will fail.
The revert behavior is intentional and correct:
- Clean state: Transaction either completes fully or reverts entirely. No partial states.
- User clarity: Buyer sees "Transaction Failed" not "Succeeded but funds pending."
- No stuck funds: With revert, ETH returns to buyer automatically. With pull-payment, funds could sit indefinitely.
- Edge case rarity: Only affects sellers who are contracts unable to receive ETH. NFT holders are typically EOAs.
- Seller's responsibility: A contract holding ERC1155 NFTs but unable to receive ETH is a bug in that contract.
Buyer loses only ~$0.01 gas on Base L2 for failed tx.
[M-02] No Minimum Price Validation for Collection Offers
Won't Fix - Backend MitigatedLocation: acceptCollectionOffer()
Original Finding:
Collection offers have no minimum price restriction at the contract level.
Architecture Context: Collection offers are stored off-chain in backend database. The contract only stores usedSignatures mapping to prevent replay.
Backend Enforcement:
POST /api/offersvalidates minimum offer price before storing- Currently enforced: 50% of floor price (configurable)
- Zero or dust offers are rejected at API level
- Spam offers never reach the database
Frontend Protection: UI shows clear price confirmation before seller signs acceptance. Warning displayed for offers significantly below floor price.
Why Not On-Chain: Backend validation is more flexible (can adjust minimum without contract upgrade). Legitimate low offers during market downturns wouldn't be blocked.
Low Severity
[L-01] getActiveListings Has O(n) Complexity
AcknowledgedLocation: getActiveListings()
- Base L2 gas is ~$0.01, making this a non-issue for view calls
- Maximum active listings expected: <100 (only ~103 Year 2 sets, ~155 Year 1 sets exist)
cleanupExpiredListings()is available for periodic maintenance- Backend already has
listingIntegrityJobrunning hourly
For this use case with limited supply, O(n) at n<200 is perfectly acceptable.
[L-02] No View Function for EIP-712 Domain Separator
False Positive AUDITOR ERRORLocation: Contract interface
Verified on mainnet:
OpenZeppelin's EIP712Upgradeable (which we inherit) already includes the eip712Domain() function per EIP-5267.
eip712Domain() function is provided by the inherited OpenZeppelin contract per EIP-5267. Apologies for the oversight.
[L-03] Owner Can Set minListingPrice to Zero
Acknowledged - IntentionalLocation: setMinListingPrice()
- Owner is hardware wallet (Ledger) with physical confirmation required
- Zero minimum might be needed for promotional periods or market conditions
- Adding artificial lower bound reduces operational flexibility
- Dust spam is deterred by gas costs even with zero minimum
Will document this as intentional owner discretion.
Informational
[I-01] Storage Gap Documentation
NotedofferNonces addition in v1.7.
[I-02] Consider Batch Listing Creation
Won't Implement[I-03] Missing Events for Some State Changes
Noted - Already CoveredListingExpiredAndCleaned event is already emitted during auto-cleanup. Current event coverage is sufficient for indexing.
[I-04] Contract Does Not Implement ERC1155Receiver
Correct - By Design5. Positive Security Findings
- Reentrancy Protection: All state-changing functions use
nonReentrantmodifier where appropriate - SafeERC20: WETH transfers use SafeERC20's
safeTransferandsafeTransferFrom - Ownable2Step: Two-step ownership transfer prevents accidental ownership loss
- Signature Replay Prevention: Uses digest-based tracking with
usedSignaturesmapping - Nonce-Based Offer Cancellation:
cancelAllOffers()provides atomic cancellation of all pending offers - UUPS Upgrade Validation:
_authorizeUpgrade()checks for non-zero address and existing code - Batch Limit on Cleanup:
MAX_CLEANUP_BATCH = 100prevents gas limit issues - Emergency Controls: Pause functionality with
cancelAllOffers()working when paused - Blacklist Functionality: Comprehensive blacklist that blocks buyers, sellers, and listing creators
- Fail-Fast Pattern: WETH transfer happens before NFT transfer in
acceptCollectionOffer - EIP-712 + EIP-5267 Compliance: Proper typed data signing with domain info exposed via
eip712Domain() - Storage Layout Preservation: New fields added at end of structs to maintain compatibility
- Off-Chain/On-Chain Hybrid: Backend handles spam prevention, contract handles security-critical validation
Security Pattern Compliance
| Pattern | Status | Notes |
|---|---|---|
| Checks-Effects-Interactions | ✓ PASS | State changes before external calls |
| Reentrancy Guards | ✓ PASS | nonReentrant on all critical functions |
| Access Control | ✓ PASS | onlyOwner on admin functions + Ownable2Step |
| Integer Overflow/Underflow | ✓ PASS | Solidity 0.8.24 built-in protection |
| Signature Verification | ✓ PASS | OpenZeppelin ECDSA.recover() with nonce |
| Upgradeable Storage | ✓ PASS | Proper storage gaps (49 slots remaining) |
| EIP-712 Domain | ✓ PASS | EIP-5267 compliant via OpenZeppelin |
6. Production Readiness Assessment
✓ PRODUCTION READY
All findings resolved. Contract is deployed and suitable for production use.
Deployment Status
| Item | Status | Notes |
|---|---|---|
| Contract Deployed | ✓ Live | December 27, 2025 |
| Source Verified | ✓ Verified | BaseScan verified |
| Owner Security | ✓ Hardware Wallet | Ledger cold storage |
| Backend Integration | ✓ Active | Offer validation, hourly cleanup job |
| Critical Vulnerabilities | ✓ None Found | - |
Recommended Monitoring
| Event | Priority | Action |
|---|---|---|
| ContractPaused | Critical | Immediate investigation |
| OwnershipTransferStarted | Critical | Verify transfer is intentional |
| Upgrade events (UUPS) | Critical | Verify new implementation |
| Large fee withdrawals | High | Alert for unusual patterns |
| BlacklistUpdated | Medium | Track for abuse patterns |
Final Verdict
Final Score: 9.0/10 (adjusted from initial 8.5)
BasePaintMarket v1.11 demonstrates mature security practices and thoughtful architectural decisions. The team's responses clarified the intentional nature of the design choices, and the false positive on L-02 has been acknowledged.
Key Strengths:
- Comprehensive reentrancy protection
- Modern OpenZeppelin v5.x patterns
- Well-designed off-chain/on-chain hybrid architecture
- Proper upgrade safety mechanisms
- Emergency controls that protect users
- Clear documentation of design decisions
Conclusion: The contract is production-ready with no outstanding security concerns. The team has demonstrated strong security awareness and the architecture appropriately balances on-chain security with off-chain flexibility.