Security Audit Report

Summary

xendune is a decentralized escrow marketplace consisting of 32 smart contracts built with Solidity ^0.8.35. This report documents all findings from the security audit, including identified vulnerabilities, design decisions, and their resolutions.

All admin operations are controlled by a Gnosis Safe multisig wallet. Owner/admin have no functions to directly withdraw or transfer user funds—all fund flows are triggered through business logic (settlement, arbitration, deposit reclamation), with every transfer emitting on-chain events for transparency.

Audit Scope

Category Details
Contracts Audited 32 Solidity contracts
Solidity Version ^0.8.35
Deployment Environment EVM-compatible chains (BSC, TRON)
Design Patterns EIP-1167 minimal proxy, CEI, pull payment
Admin Model Gnosis Safe multisig

Contracts in Scope

Core: PlatformSettings, PlatformFeeSplitter, ArchiveStore, ProductLib

Product System: ProductFactory, ProductFactoryReader, ProductFactoryKeywords, ServiceLocationIndex, PhysicalProductTemplate, VirtualProductTemplate, ServiceProductTemplate, WantToBuyTemplate

C2C Trading: C2CFactory, C2CFactoryReader, C2CSellOrderTemplate, C2CBuyOrderTemplate, C2CTradeTemplate

Auctions: AuctionFactory, AuctionFactoryReader, AuctionTemplate

Shuifang Currency Exchange: ShuifangFactory, ShuifangFactoryReader, ShuifangEscrowTemplate, ShuifangKeywords

Community Arbitration: CommunityArbitrationFactory, CommunityArbitrationTemplate

Deposits: DepositFactory, MerchantDepositTemplate

Infrastructure: KeywordWeight, KeywordAuction, InviteRegistry, CooldownManager

Findings Summary

Severity Count Fixed Acknowledged
High 0
Medium 4 3 1
Low 5 1 4
Informational 3 0 3
Gas Optimization 5 0 5

Medium Severity

M-01: Centralization Risk—Admin Privileges

Location: PlatformSettings (all admin privilege functions)

Description: Owner role has significant privileges including fee rate configuration, role assignment, blacklist management, and forced delisting.

Mitigations:

  1. Owner is a Gnosis Safe multisig wallet—all operations require multi-party confirmation
  2. Owner/admin have no functions to directly withdraw or transfer user funds
  3. All fund flows are triggered through business logic: arbitration resolution, trade settlement, zombie deposit reclamation
  4. Every fund transfer emits on-chain events ensuring full transparency
  5. Admin operations limited to: configuration changes, dispute resolution, keyword management, and emergency delisting

Status: Acknowledged—mitigated via multisig and architectural constraints

M-02: CooldownManager—tradeEnded Does Not Reset Cooldown

Location: CooldownManager.tradeEnded()

Description: tradeEnded() callback only emits an event but does not reset the cooldown timer.

Design Rationale: This is intentional. Cooldown is a one-time 24-hour threshold for new accounts. If every completed trade reset the cooldown, it would create a DoS vector—completing trades would re-lock accounts. Cooldown serves as an initial anti-fraud barrier, not a persistent restriction.

Status: Acknowledged—by design

M-03: MerchantDepositTemplate—deduct() Missing Zero-Address Check

Location: MerchantDepositTemplate.deduct()

Description: deduct() function does not verify recipient address is non-zero when transferring USDT, potentially burning funds.

Fix Applied:

function deduct(uint256 amount, address to) external onlyAuthorizedProduct nonReentrant {
    if (amount == 0) revert ZeroAmount();
    if (to == address(0)) revert ZeroAddress();  // Added
    if (status != DepositStatus.Frozen && status != DepositStatus.Active) revert InvalidStatus();
    ...
}

Status: Fixed

M-04: Hardcoded USDT Address

Location: ProductFactory, C2CFactory, AuctionFactory, DepositFactory, CooldownManager, KeywordAuction, KeywordWeight, PlatformSettings

Description: In testnet deployments, USDT contract address was hardcoded as constant rather than passed via constructor parameter. This prevents multi-chain deployment since each chain has different USDT addresses.

Fix Applied: In production versions, all hardcoded USDT_ADDRESS constants have been removed. USDT address is now passed via constructor parameter for each contract requiring it, supporting deployment to any EVM-compatible chain (BSC, TRON, etc.) with the correct USDT address for that chain.

Status: Fixed


Low Severity

L-01: PlatformSettings—Missing Two-Step Ownership Transfer

Location: PlatformSettings.transferOwnership()

Description: Ownership transfer is single-step. If new owner address is incorrect, ownership is permanently lost.

Mitigation: Ownership will be transferred to a Gnosis Safe multisig wallet. The multisig itself is a multi-step confirmation mechanism—ownership transfer transactions require approval from multiple signers, effectively providing the same protection as two-step transfer.

Status: Acknowledged—mitigated via multisig

L-02: ProductFactory—Unbounded Loop in delistAllProducts()

Location: ProductFactory.delistAllProducts()

Description: Function iterates through all active products for a seller, theoretically risking gas exhaustion.

Mitigation: MAX_PRODUCTS_PER_SELLER = 5 enforced at product creation. Loop iterates maximum 5 times, well within block gas limits. This constant cannot be changed without redeploying contracts.

Status: Acknowledged—bounded by design

L-03: InviteRegistry—Circular Referral Check Limited to 2 Levels

Location: InviteRegistry.register()

Description: Circular referral check only validates 1-2 level loops (A→B→A), not deeper chains.

Mitigation: System only supports two-tier referral rewards (direct inviter only). Deeper circular chains have no economic impact since rewards only distributed to direct inviter. Checking deeper chains would increase gas cost without security benefit.

Status: Acknowledged—by design

L-04: ProductFactoryReader—Independently Deployed Reader Contract

Location: ProductFactoryReader

Description: Contract deployed independently and called directly by frontend rather than integrated into ProductFactory.

Design Rationale: This is an intentional architectural decision. Separating read-heavy batch operations (auto-receive triggers, batch queries) into an independent contract keeps the main ProductFactory within deployment size limits and allows independent upgrades of view logic without affecting core state.

Status: Acknowledged—by design

L-05: PlatformSettings—totalDepositsHeld Statistical Variable

Location: PlatformSettings.totalDepositsHeld

Description: totalDepositsHeld variable tracks aggregate deposit statistics but does not hold actual assets. Divergence between this counter and actual deposit balances could mislead monitoring tools.

Mitigation: This is purely a statistical counter for dashboard display. It uses safe arithmetic (checked subtraction) and does not affect actual fund flows. Deposit balances are tracked individually in each MerchantDepositTemplate contract.

Status: Acknowledged—statistical purpose only


Informational

I-01: Reentrancy Lock Using uint256 Not bool

Location: All template contracts (PhysicalProductTemplate, VirtualProductTemplate, ServiceProductTemplate, C2CTradeTemplate, C2CSellOrderTemplate, AuctionTemplate, MerchantDepositTemplate)

Description: Reentrancy lock uses uint256 (0→1→2→1) rather than the more common bool pattern.

Design Rationale: This is best practice for EIP-1167 clones. Clone contracts have storage initialized to zero. bool defaults to false, unable to distinguish "uninitialized" from "unlocked". The uint256 pattern sets lock to 1 in initialize(), then toggles between 1 (unlocked) and 2 (locked) during function execution. This ensures reentrancy lock works correctly in cloned contracts.

Status: Acknowledged—EIP-1167 best practice

I-02: Anti-Bot noContract Modifier

Location: User-facing functions in all template contracts

Description: noContract modifier blocks smart contract calls by checking msg.sender != tx.origin, with whitelist exceptions.

Design Rationale: This defends against flash loan attacks and automated exploits. Whitelisted contracts (set by admin) can bypass this check for legitimate integrations.

Status: Acknowledged—intentional security measure

I-03: Pull Payment Pattern for Failed Transfers

Location: ProductLib._safeTransferToUser(), all template contracts

Description: If USDT transfer fails during settlement, amount is stored in pendingWithdrawals[recipient] rather than reverting the entire transaction.

Design Rationale: This prevents a single failed transfer (e.g., to a contract rejecting transfers) from blocking the entire settlement flow. Recipients can later call claimPending() to retrieve funds.

Status: Acknowledged—intentional safety pattern


Gas Optimization Notes

G-01: _clone() Function Duplicated Across Factory Contracts

Location: ProductFactory, C2CFactory, AuctionFactory, DepositFactory

Description: EIP-1167 _clone() function duplicated in each factory rather than inherited from shared base class.

Design Rationale: Each factory deploys independently with no shared inheritance chain. Clone function is only 10 lines of assembly. Introducing a base contract would add deployment complexity and upgrade coupling for negligible code savings.

Status: Acknowledged—intentional design

G-02: Storage Variables Not Cached to Local Variables

Location: AuctionTemplate.bid() and similar functions

Description: State variables like settings and usdt are read multiple times within a single function without caching to local variables.

Design Rationale: After first SLOAD (cold access, 2100 gas), subsequent reads of same storage slot are warm access (100 gas). Local caching saves minimal gas (≈80 gas per additional read), insufficient to justify reduced readability.

Status: Acknowledged—negligible optimization

G-03: Silent try/catch in Batch Operations

Location: ProductFactoryReader.batchAutoReceive()

Description: Batch auto-receive function uses silent try/catch blocks, swallowing individual order failures.

Design Rationale: This is intentional. Batch operations should not revert entirely due to one order failure (e.g., state changed between read and execution). Function returns successCount, which off-chain monitoring can use to detect and investigate failures.

Status: Acknowledged—intentional design

G-04: On-Chain Storage of Image URLs

Location: AuctionTemplate, PhysicalProductTemplate, VirtualProductTemplate, ServiceProductTemplate

Description: Image URLs stored on-chain as string arrays with high gas cost.

Mitigation: All contracts enforce maximum image limits (9-10). Gas cost is bounded and acceptable—on-chain storage ensures image references cannot be censored or lost.

Status: Acknowledged—bounded by MAX_IMAGES

G-05: recordArbitration Naming

Location: PlatformSettings.recordArbitration()

Description: Function name recordArbitration could be misinterpreted as recording only negative outcomes, but it actually records all arbitration events regardless of outcome.

Design Rationale: Naming is clear in business context—"record that an arbitration occurred for this merchant". Function increments a counter used by KeywordWeight ranking algorithm as a negative signal. Arbitrations are recorded regardless of win/loss because frequent disputes (even if won) indicate potential issues.

Status: Acknowledged—naming appropriate in context


Architectural Security Measures

Access Control Hierarchy

Role Assignment Permissions Requires Multisig
Owner Gnosis Safe Global configuration, role management Yes
Admin Set by Owner Arbitration, keyword approval, forced operations Yes (via Owner)
CS Set by Owner Arbitration assistance Yes (via Owner)
Factory Automatic Product/trade registration N/A (inter-contract calls)
Authorized Product Set by Factory Deposit deductions N/A (inter-contract calls)

Fund Safety Guarantees

  1. No admin withdrawal: No functions exist allowing owner/admin to move user funds to arbitrary addresses
  2. Escrow isolation: Each product/trade has independent contract and isolated funds
  3. Deposit protection: Merchant deposits can only be deducted via arbitration resolution by authorized product contracts
  4. Settlement atomicity: Fee distribution, referral rewards, and seller payment occur in single transaction
  5. Failure recovery: Failed transfers stored in pendingWithdrawals for later claim, funds never lost

Design Patterns Used


Conclusion

The xendune protocol demonstrates a well-architected escrow system with appropriate security measures. Of the four medium-severity findings, three have been fixed (zero-address validation, hardcoded USDT removal, input checks), with the remaining one (centralization risk) mitigated via Gnosis Safe multisig and non-custodial architecture. The codebase follows mature Solidity security patterns with EIP-1167 clone architecture correctly implemented across all template contracts.

The most important security feature is the non-custodial design: admin roles cannot withdraw user funds, all fund flows are governed by smart contract logic, with on-chain event tracking for audit trails.