5. Technical Components5.2 FhenixFairMarket.sol

5.2. FhenixFairMarket.sol (Core Logic)

FhenixFairMarket.sol serves as the primary implementation contract for the protocol’s UUPS proxy architecture. It encapsulates the entire auction lifecycle, from cryptographic escrow locking and sealed-bid submission to asynchronous CoFHE resolution and pull-based settlement. By strictly separating executable logic from persistent storage, this contract ensures safe, zero-downtime upgrades while enforcing mathematical guarantees around bid solvency, state transitions, and emergency fund recovery.

All cryptographic operations are routed through ICofheAdapter, and all state mutations are guarded by role-based modifiers and reentrancy protections, making it the foundational trust anchor of the Fhenix-FairMarket ecosystem.

Core Design Principles

PrincipleTechnical Implementation
UUPS Logic/Storage SeparationInherits UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable. State lives in proxy; logic executes via delegatecall.
State-Guarded TransitionsEvery function validates AuctionState via custom modifiers (onlyActive, onlyResolving, notFinalized). Unauthorized jumps revert instantly.
O(1) Encrypted StorageBids store only a bytes32 keccak256 hash of the InEuint32 ciphertext. No FHE objects or plaintext values persist on-chain.
Adapter-Isolated CryptographyZero direct imports of @fhenixprotocol/cofhe-contracts. All FHE calls route through ICofheAdapter to prevent SDK upgrade breakage.
Pull-Over-Push SettlementRefunds use individual claimRefund() paths with hasWithdrawn mapping. State updates execute before external value transfers.
Dynamic Emergency FallbacktriggerFallbackVoid() activates based on Moving Time Average × 1.5 of block timestamps, guaranteeing 100% liquidity recovery during sequencer/AVS outages.

State Machine & Lifecycle Architecture

The contract enforces a strict 6-state lifecycle to ensure deterministic behavior across asynchronous resolution and emergency recovery.

StateDescriptionPermitted Actions
CREATEDAuction initialized, NFT pending locklockEscrow(), createAuction()
ACTIVEBidding open, escrow accumulatingplaceBid(), cancelAuction(), triggerFinalize()
RESOLVINGBids closed, off-chain CoFHE processingsubmitResolution(), triggerFallbackVoid()
FINALIZEDWinner declared, refunds openclaimRefund() (pull), NFT transfer to winner
CANCELLEDSeller aborted pre-closureclaimRefund() (pro-rata from SlashedPot)
VOIDEDEmergency timeout triggeredclaimRefund() (100% recovery), FHE engine disabled

️ Critical Function Reference

1. Encrypted Bid Submission (placeBid)

Executes the cryptographic solvency gate and registers the bid hash with O(1) gas complexity.

function placeBid(uint256 auctionId, InEuint32 calldata encryptedBid) external nonReentrant onlyActive(auctionId) {
 Auction storage auction = auctions[auctionId];
 require(block.timestamp < auction.endTime, "Auction expired");
 
 // Adapter-Isolated Solvency Check (Zero plaintext leak)
 ICofheAdapter(cofheAdapter).verifySolvency(encryptedBid, escrowBalances[msg.sender]);
 
 // O(1) Storage: Only ciphertext hash persists
 bytes32 bidHash = keccak256(abi.encode(encryptedBid));
 auction.ciphertextHashes.push(bidHash);
 
 emit BidPlaced(auctionId, msg.sender, bidHash);
}

2. Asynchronous Finalization (triggerFinalize)

Locks the auction state and emits the DecryptionRequested event to trigger off-chain processing.

function triggerFinalize(uint256 auctionId) external nonReentrant {
 require(auctions[auctionId].state == AuctionState.ACTIVE, "Not active");
 require(block.timestamp >= auctions[auctionId].endTime, "Not ended yet");
 
 // Atomic state transition
 auctions[auctionId].state = AuctionState.RESOLVING;
 
 // Dispatch to off-chain CoFHE layer
 emit DecryptionRequested(auctionId, auctions[auctionId].ciphertextHashes);
 
 // Keeper incentive (0.2%) distributed via SettlementEngine
 _distributeKeeperBounty(auctionId, msg.sender);
}

3. AVS-Verified Resolution (submitResolution)

Accepts off-chain results only after cryptographic proof verification.

function submitResolution(uint256 auctionId, bytes32 winnerCiphertext, bytes calldata avsProof) external nonReentrant {
 require(auctions[auctionId].state == AuctionState.RESOLVING, "Not resolving");
 
 // EigenLayer AVS Fraud Proof verification
 ISettlementEngine(settlementEngine).verifyResolution(auctionId, winnerCiphertext, avsProof);
 
 auctions[auctionId].state = AuctionState.FINALIZED;
 auctions[auctionId].winnerCiphertext = winnerCiphertext;
 
 emit AuctionFinalized(auctionId, winnerCiphertext);
}

4. Pull-Based Refund (claimRefund)

State-first mutation prevents reentrancy and Out-of-Gas cascades.

function claimRefund(uint256 _auctionId) external nonReentrant {
 AuctionState state = auctions[_auctionId].state;
 require(state == FINALIZED || state == CANCELLED || state == VOIDED, "Invalid state");
 require(!auctions[_auctionId].hasWithdrawn[msg.sender], "Already withdrawn");
 
 uint256 amount = escrowBalances[msg.sender];
 require(amount > 0, "No balance");
 
 // ️ CRITICAL: State mutated BEFORE external transfer
 escrowBalances[msg.sender] = 0;
 auctions[_auctionId].hasWithdrawn[msg.sender] = true;
 
 (bool success, ) = msg.sender.call{value: amount}("");
 require(success, "Refund failed");
 
 emit RefundClaimed(_auctionId, msg.sender, amount);
}

5. Dynamic Emergency Void (triggerFallbackVoid)

Permissionless recovery mechanism activated during infrastructure degradation.

function triggerFallbackVoid(uint256 auctionId) external {
 Auction storage auction = auctions[auctionId];
 require(auction.state == AuctionState.RESOLVING, "Not resolving");
 
 // Network-volatility-aware threshold
 uint256 timeout = _getResolutionTimeout();
 require(block.timestamp > auction.endTime + timeout, "Timeout not exceeded");
 
 auction.state = AuctionState.VOIDED;
 auction.fheEngineActive = false; // Disables further decryption
 auction.lastBlockTimestamp = block.timestamp;
 
 emit AuctionVoided(auctionId, block.timestamp, timeout);
}
 
function _getResolutionTimeout() internal view returns (uint256) {
 uint256 lastTime = lastBlockTimestamp > 0 ? lastBlockTimestamp : block.timestamp - 12;
 return (block.timestamp - lastTime) * 1.5; // Moving Time Average × 1.5
}

Storage Layout & Proxy Compatibility

To maintain UUPS upgrade safety, the contract uses OpenZeppelin storage slots and avoids layout collisions:

struct Auction {
 address nftContract;
 uint256 tokenId;
 address payable seller;
 uint256 endTime;
 uint256 sellerDeposit;
 AuctionState state;
 bool isVickrey;
 bool fheEngineActive;
 bytes32 winnerCiphertext;
 uint256 lastBlockTimestamp; // For DynamicTimeout calculation
 mapping(address => uint256) public escrowBalances;
 mapping(address => bool) public hasWithdrawn;
 bytes32[] public ciphertextHashes;
}
 
// Proxy-reserved slots (EIP-1967)
// _owner, _implementation, _proxiableUUID are strictly isolated

️ Security & Economic Guarantees

  1. Reentrancy Immunity: All external interactions (call{value: amount}) are preceded by state mutations (hasWithdrawn = true, escrowBalances = 0). ReentrancyGuardUpgradeable provides a secondary defense layer.
  2. Zero Plaintext Leakage: BidPlaced, DecryptionRequested, and AuctionFinalized events emit only bytes32 hashes and state identifiers. CI lint rules enforce strict compliance.
  3. Solvency Enforcement: FHE.lte executes homomorphically against escrowBalances. Invalid bids revert instantly without consuming coprocessor resources or gas beyond the base transaction.
  4. Graceful Degradation: If triggerFinalize() fails due to network congestion, triggerFallbackVoid() provides a deterministic exit path. Capital recovery is prioritized over cryptographic settlement (Funds Over Privacy).
  5. Upgrade Safety: _authorizeUpgrade() restricts execution to the governance multisig. All upgrades pass through a 48h Timelock, allowing community review before logic swaps.

Audit Gate Compliance (P0)

Progression from Phase 1/2 is strictly blocked until all core logic P0 items pass:

  • [] Contract inherits UUPSUpgradeable and blocks upgradeToAndCall for non-owners.
  • [] Zero direct import of @fhenixprotocol/cofhe-contracts in core logic; all calls route through ICofheAdapter.
  • [] FHE.lte solvency gate reverts instantly if encryptedBid > escrowBalances.
  • [] claimRefund() updates hasWithdrawn before call{value: amount}. Zero for/while loops in payout logic.
  • [] triggerFallbackVoid() activates only after Moving Time Average × 1.5 threshold, disabling FHE engine and unlocking 100% liquidity.
  • [] All 6 state transitions are guarded by modifiers; unauthorized jumps revert with precise error strings.
  • [] initialize() executes exactly once; storage layout matches EIP-1967 proxy standards without collisions.
  • [] No plaintext bid values or exact settlement prices emitted in Events.

Next Steps