📅 Audit Timeline

Dec 29, 2025
Initial audit report delivered
Dec 29, 2025
Team response received with clarifications
Dec 29, 2025
Final report issued (this document)
Final Security Score
9.0/10
PRODUCTION READY ✓

Score adjusted from 8.5 → 9.0 after team clarifications resolved L-02 (false positive) and confirmed intentional design decisions

Access Control
9/10
Reentrancy
10/10
Logic
9/10
Upgradeability
9/10
Token Handling
9/10
Code Quality
9/10

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

0
Critical
0
High
0
Medium (Unresolved)
0
Low (Unresolved)
9
Resolved/Acknowledged
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 Design

Location: _distributeFunds() and buyListing()

Original Finding:

If the seller's address is a contract that reverts on receiving ETH, the entire transaction will fail.

Team Response - Won't Fix:

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.

Auditor Assessment: Team's reasoning is sound. The revert-on-failure pattern provides cleaner UX than pull payments for this use case. The edge case is rare and the impact is limited to gas costs. Finding accepted as intentional design.

[M-02] No Minimum Price Validation for Collection Offers

Won't Fix - Backend Mitigated

Location: acceptCollectionOffer()

Original Finding:

Collection offers have no minimum price restriction at the contract level.

Team Response - Backend Enforcement:

Architecture Context: Collection offers are stored off-chain in backend database. The contract only stores usedSignatures mapping to prevent replay.

Backend Enforcement:

  • POST /api/offers validates 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.

Buyer creates offer → Backend validates min price → Stored in DB ↓ (rejected if too low) Seller accepts → Contract verifies signature → Trade executes
Auditor Assessment: The off-chain architecture context was not initially apparent from contract analysis alone. With backend enforcement at 50% floor price, frontend warnings, and explicit seller confirmation, the risk is adequately mitigated. Finding accepted as intentional architecture decision.

Low Severity

[L-01] getActiveListings Has O(n) Complexity

Acknowledged

Location: getActiveListings()

Team Response:
  • 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 listingIntegrityJob running hourly

For this use case with limited supply, O(n) at n<200 is perfectly acceptable.

Auditor Assessment: Given the limited supply context (~258 total sets), the O(n) complexity is acceptable. Finding acknowledged with no action required.

[L-02] No View Function for EIP-712 Domain Separator

False Positive AUDITOR ERROR

Location: Contract interface

Team Response - Already Implemented:

Verified on mainnet:

cast call 0xB0897037052BB9104CcDF743358ea4f91990A362 "eip712Domain()" --rpc-url https://mainnet.base.org Returns: name: "BasePaintMarket" version: "1" chainId: 8453 verifyingContract: 0xB0897037052BB9104CcDF743358ea4f91990A362

OpenZeppelin's EIP712Upgradeable (which we inherit) already includes the eip712Domain() function per EIP-5267.

Auditor Assessment: This finding is withdrawn. The 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 - Intentional

Location: setMinListingPrice()

Team Response:
  • 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.

Auditor Assessment: Reasonable flexibility for owner operations. Hardware wallet requirement provides adequate protection against accidental misconfiguration.

Informational

[I-01] Storage Gap Documentation

Noted
Team Response: Gap consumption is tracked in contract comments. Current: 49 slots after offerNonces addition in v1.7.

[I-02] Consider Batch Listing Creation

Won't Implement
Team Response: Users rarely list both Year 1 and Year 2 simultaneously. Two transactions is acceptable UX. Adding batch function increases contract size and complexity for marginal benefit.

[I-03] Missing Events for Some State Changes

Noted - Already Covered
Team Response: ListingExpiredAndCleaned event is already emitted during auto-cleanup. Current event coverage is sufficient for indexing.

[I-04] Contract Does Not Implement ERC1155Receiver

Correct - By Design
Team Response: This is intentional. Approval-based design means NFTs never enter the contract. The contract should NOT implement ERC1155Receiver.
Auditor Assessment: Correct. This was noted as informational and the design is appropriate.

5. Positive Security Findings

  • Reentrancy Protection: All state-changing functions use nonReentrant modifier where appropriate
  • SafeERC20: WETH transfers use SafeERC20's safeTransfer and safeTransferFrom
  • Ownable2Step: Two-step ownership transfer prevents accidental ownership loss
  • Signature Replay Prevention: Uses digest-based tracking with usedSignatures mapping
  • 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 = 100 prevents 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.