# audit-auction

Audits Solidity auction mechanisms for manipulation vulnerabilities including self-bidding to reset auction timer, auction start during L2 sequencer downtime affecting timing fairness, insufficient auction length validation allowing very short auctions for immediate seizure, and off-by-one errors allowing seizure during active auction period (project)

- **Kind:** skill
- **Source:** https://github.com/auditmos/skills
- **Page:** https://forefy.com/skills/6c524cfd-d492-40a9-9c5b-863f0eb9f78c
- **API (JSON + files):** https://forefy.com/api/asr/6c524cfd-d492-40a9-9c5b-863f0eb9f78c

---

## SKILL.md

---
name: audit-auction
description: Audits Solidity auction mechanisms for manipulation vulnerabilities including self-bidding to reset auction timer, auction start during L2 sequencer downtime affecting timing fairness, insufficient auction length validation allowing very short auctions for immediate seizure, and off-by-one errors allowing seizure during active auction period (project)
allowed-tools: Read, Grep, Glob
license: MIT
compatibility: Designed for Claude Code (or similar products)
metadata:
  author: Tomasz Kowalczyk (tom@auditmos.com)
  version: "1.0"
---

# Auction Manipulation Auditor

## When to Use
- Auditing auction mechanisms, Dutch auctions, liquidation auctions
- User mentions: auction, bid, Dutch auction, auction timer, auction length, sequencer downtime, seizure
- Analyzing auction timing, bid validation, auction parameters
- Reviewing liquidation auctions, loan auctions, NFT auctions

## Audit Workflow

**IMPORTANT: Announce skill usage at the start of analysis**

Begin with: "I'm using the **audit-auction** skill to analyze this contract for auction manipulation vulnerabilities..."

1. **Scan for auction operations**
   - Search: `auction`, `bid`, `startAuction`, `endAuction`, `auctionEnd`, `auctionLength`, `buyLoan`, `seize`, `settle`
   - Focus: timer resets, length validation, timestamp checks, sequencer integration

2. **Check against vulnerability patterns**
   - Reference `reference.md` for complete checklist
   - Compare code against `example.md`

3. **Validate exploitability**
   - **Check access control first** - grep for `onlyOwner|onlyAdmin|onlyGovernance` modifiers
   - Can non-privileged actors exploit auction manipulation?
   - Can borrower reset auction by self-bidding?
   - Can auctions start during sequencer downtime?
   - Are auction lengths validated (minimum duration)?
   - Are timestamp comparisons correct (no off-by-one)?
   - Verify no compensating protections exist
   - Downgrade severity if admin-only unless enables borrower manipulation

4. **Generate report**
   - Use deliverable template below
   - Include manipulation scenarios and PoC
   - Rank by severity

## Core Vulnerability Patterns

See `reference.md` for full checklist. Key patterns:

1. Self-bidding to reset auction → borrower buys own loan to restart timer indefinitely
2. Auction start during sequencer downtime → L2 sequencer issues affect fairness
3. Insufficient auction length validation → 1-second auctions allow immediate seizure
4. Auction seizure during active period → off-by-one allows premature seizure

**Code examples:** See `example.md`

## Severity Criteria

**Critical:** Self-bidding allowing infinite auction extensions, off-by-one enabling immediate seizure bypassing auction period, **MUST be exploitable by non-privileged actors**
**High:** Insufficient length validation allowing very short auctions, sequencer downtime affecting auction fairness on L2, **MUST be exploitable by non-privileged actors**
**Medium:** Suboptimal auction parameters, missing events for auction state changes, **admin-only auction configuration issues with cascading borrower impact**
**Low:** Gas inefficiencies in auction logic, missing view functions, **admin-only parameter issues without immediate borrower impact**

**IMPORTANT:** Admin-only auction functions (onlyOwner, onlyAdmin, onlyGovernance) are **MEDIUM or LOW severity** unless:
- Admin can start auctions during sequencer downtime without checks
- Missing validation in auction length setters allows admin to set 1-second auctions
- Admin auction timing configuration enables unfair seizure of borrower collateral

## False Positives - Do NOT Flag

- Protocols with explicit self-bidding allowed (documented)
- L1-only deployments (no sequencer concerns)
- Admin-only auction start (trusted operation)
- Intentional short auctions with documented rationale
- Test/mock auction contracts
- **Admin-only auction start functions** (onlyOwner, onlyAdmin) with sequencer uptime checks on L2
- Governance-controlled auction length parameters with minimum bounds validation (e.g., >= 1 hour)
- Admin functions for emergency auction settlement with documented safeguards

## Deliverable Format

**MANDATORY:** Before deliverable, verify each `checklist.md` item against codebase. Flag violations as findings.

Use template: `templates/report-template.md`

Each finding includes: severity, pattern #, file/lines, description, vulnerable code, manipulation scenario, PoC, remediation.

## Key Principles

- **No self-bidding** - prevent borrower from bidding on own auction
- **Sequencer awareness** - check sequencer uptime before auction start on L2
- **Minimum duration** - enforce reasonable minimum (e.g., 1 hour)
- **Correct comparisons** - use >= not >, avoid off-by-one
- **Timer integrity** - prevent auction timer resets

## Output Guidelines

**DO:**
- Reference specific lines and functions
- Provide manipulation scenarios with timing
- Show PoCs demonstrating auction abuse
- Calculate impact of timing exploits
- Map all auction state transitions

**DON'T:**
- Report intentional design choices (short auctions with rationale)
- Flag missing features with alternative mechanisms
- Ignore timestamp precision (critical for fairness)
- Miss edge cases (exact timestamp boundaries)

## checklist.md

# Auction Manipulation Security Checklist

Verify each item before finalizing audit report:

- [ ] **No self-bidding:** Borrower/owner cannot bid on own auction or reset timer
- [ ] **Sequencer check:** Sequencer uptime validated before auction start on L2 chains
- [ ] **Minimum length:** Auction length has enforced minimum (e.g., 1 hour)
- [ ] **Correct timestamp comparison:** Auction end check uses >= not > (no off-by-one)

## example.md

# Auction Manipulation Vulnerability Examples

## Pattern #1: Self-Bidding to Reset Auction

### VULNERABLE
```solidity
contract VulnerableSelfBid {
    struct Auction {
        address borrower;
        uint256 startTime;
        uint256 duration;
        uint256 highestBid;
        address highestBidder;
    }

    mapping(uint256 => Auction) public auctions;

    // ISSUE: No check preventing borrower from bidding
    function bid(uint256 auctionId, uint256 amount) external {
        Auction storage auction = auctions[auctionId];
        require(block.timestamp < auction.startTime + auction.duration, "Ended");
        require(amount > auction.highestBid, "Bid too low");

        auction.highestBid = amount;
        auction.highestBidder = msg.sender;

        // Borrower can bid on own loan
        // Each bid extends "effective" auction by resetting competitive pressure
    }

    function settleAuction(uint256 auctionId) external {
        Auction storage auction = auctions[auctionId];
        require(block.timestamp >= auction.startTime + auction.duration, "Active");

        // Transfer to highest bidder
        // If borrower is highest bidder, they "buy" own loan
        // Can repeat by refinancing and starting new auction
    }

    // Attack:
    // 1. Auction starts for underwater loan
    // 2. Legitimate bidder bids 80 ETH
    // 3. Borrower bids 81 ETH (buying own loan)
    // 4. Auction ends, borrower "pays" themselves
    // 5. Borrower can refinance and repeat
    // 6. Extends underwater position indefinitely
}
```

### FIXED
```solidity
contract FixedSelfBid {
    struct Auction {
        address borrower;
        uint256 startTime;
        uint256 duration;
        uint256 highestBid;
        address highestBidder;
    }

    mapping(uint256 => Auction) public auctions;

    function bid(uint256 auctionId, uint256 amount) external {
        Auction storage auction = auctions[auctionId];
        require(block.timestamp < auction.startTime + auction.duration, "Ended");
        require(amount > auction.highestBid, "Bid too low");

        // Prevent self-bidding
        require(msg.sender != auction.borrower, "Cannot bid on own auction");

        auction.highestBid = amount;
        auction.highestBidder = msg.sender;
    }
}
```

## Pattern #2: Auction Start During Sequencer Downtime

### VULNERABLE
```solidity
contract VulnerableL2Auction {
    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    // ISSUE: On Arbitrum/Optimism, no sequencer check
    function startAuction(uint256 loanId) external {
        auctions[loanId] = Auction({
            startTime: block.timestamp,
            duration: 24 hours
        });

        // Scenario:
        // 1. Sequencer down for 2 hours
        // 2. Auction starts at T=0 (in transaction queue)
        // 3. Sequencer restarts at T=2h
        // 4. Auction processes with startTime = T=0
        // 5. First bidder after restart has only 22h window
        // 6. Unfair advantage to whoever can bid first after restart
    }
}
```

### FIXED
```solidity
contract FixedL2Auction {
    AggregatorV3Interface public sequencerUptimeFeed;
    uint256 public constant GRACE_PERIOD = 1 hours;

    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    function startAuction(uint256 loanId) external {
        // Check sequencer status on L2
        (, int256 answer, uint256 startedAt, , ) = sequencerUptimeFeed.latestRoundData();

        // Sequencer must be up (answer == 0)
        require(answer == 0, "Sequencer down");

        // Grace period after sequencer restart
        require(
            block.timestamp >= startedAt + GRACE_PERIOD,
            "Grace period active"
        );

        // Now safe to start auction
        auctions[loanId] = Auction({
            startTime: block.timestamp,
            duration: 24 hours
        });
    }
}
```

## Pattern #3: Insufficient Auction Length Validation

### VULNERABLE
```solidity
contract VulnerableLength {
    struct Auction {
        uint256 startTime;
        uint256 duration; // No minimum!
    }

    mapping(uint256 => Auction) public auctions;

    // ISSUE: No minimum auction length
    function startAuction(uint256 loanId, uint256 duration) external {
        auctions[loanId] = Auction({
            startTime: block.timestamp,
            duration: duration // Can be 1 second!
        });
    }

    function seizeCollateral(uint256 loanId) external {
        Auction storage auction = auctions[loanId];
        require(block.timestamp >= auction.startTime + auction.duration, "Active");

        // Transfer collateral to caller
    }

    // Attack:
    // 1. Liquidator starts auction with duration = 1 second
    // 2. Waits 1 second
    // 3. Immediately seizes collateral
    // 4. No competitive bidding occurred
    // 5. Borrower gets unfair price
}
```

### FIXED
```solidity
contract FixedLength {
    uint256 public constant MIN_AUCTION_DURATION = 1 hours;
    uint256 public constant MAX_AUCTION_DURATION = 7 days;

    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    function startAuction(uint256 loanId, uint256 duration) external {
        // Enforce minimum and maximum
        require(
            duration >= MIN_AUCTION_DURATION,
            "Duration too short"
        );
        require(
            duration <= MAX_AUCTION_DURATION,
            "Duration too long"
        );

        auctions[loanId] = Auction({
            startTime: block.timestamp,
            duration: duration
        });
    }

    // Minimum 1 hour ensures competitive bidding
}
```

## Pattern #4: Auction Can Be Seized During Active Period

### VULNERABLE
```solidity
contract VulnerableOffByOne {
    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    function seizeCollateral(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // ISSUE: Using > instead of >=
        require(
            block.timestamp > auction.startTime + auction.duration,
            "Auction active"
        );

        // Seizure allowed
    }

    // Problem:
    // If auction ends at timestamp T
    // This check allows seizure at timestamp T
    // But auction should be active until T inclusive
    // Bidder at exactly T gets front-run by seize

    // Example:
    // startTime = 1000
    // duration = 3600 (1 hour)
    // endTime = 4600
    //
    // At timestamp 4600:
    // - Bidder submits bid (expects auction active)
    // - Seize transaction front-runs with timestamp 4600
    // - 4600 > 4600 is FALSE
    // - Wait, 4600 > 4600 is FALSE but should be checked at 4601
    //
    // Actually at timestamp 4601:
    // - 4601 > 4600 is TRUE
    // - Auction can be seized
    // - But bidder at 4600 thought they had until 4600 inclusive
}
```

### FIXED
```solidity
contract FixedOffByOne {
    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    function seizeCollateral(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Use >= to ensure auction fully complete
        require(
            block.timestamp >= auction.startTime + auction.duration,
            "Auction active"
        );

        // Now seizure only allowed after auction end
    }

    // At endTime = 4600:
    // - 4600 >= 4600 is TRUE
    // - But bids should still be accepted at 4600
    //
    // Better: bid check uses <, seize uses >=
    function bid(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Bid allowed before end
        require(
            block.timestamp < auction.startTime + auction.duration,
            "Auction ended"
        );
    }

    // Timeline:
    // T < 4600: Bids allowed, seize not allowed
    // T = 4600: Bids not allowed, seize not allowed (grace)
    // T >= 4600: Bids not allowed, seize allowed
    //
    // Actually at T=4600:
    // bid: 4600 < 4600 = FALSE (no bid)
    // seize: 4600 >= 4600 = TRUE (can seize)
    //
    // Issue: No buffer at exact boundary
    // Solution: Add buffer period
}
```

### BEST PRACTICE
```solidity
contract BestPracticeTimestamp {
    struct Auction {
        uint256 startTime;
        uint256 duration;
    }

    mapping(uint256 => Auction) public auctions;

    function bid(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Strict: bid allowed only before end
        require(
            block.timestamp < auction.startTime + auction.duration,
            "Auction ended"
        );
    }

    function seizeCollateral(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Strict: seize allowed only after end
        require(
            block.timestamp > auction.startTime + auction.duration,
            "Auction active"
        );

        // This creates 1-second gap at exact boundary
        // At T = endTime:
        //   - bid: T < endTime = FALSE (rejected)
        //   - seize: T > endTime = FALSE (rejected)
        // At T = endTime + 1:
        //   - seize: T > endTime = TRUE (allowed)
    }
}
```

## Complete Auction Example

### VULNERABLE
```solidity
contract CompleteVulnerableAuction {
    struct Auction {
        address borrower;
        uint256 startTime;
        uint256 duration; // No minimum
        uint256 highestBid;
        address highestBidder;
    }

    mapping(uint256 => Auction) public auctions;

    function startAuction(uint256 loanId, uint256 duration) external {
        // No sequencer check on L2
        // No minimum duration
        auctions[loanId] = Auction({
            borrower: msg.sender,
            startTime: block.timestamp,
            duration: duration,
            highestBid: 0,
            highestBidder: address(0)
        });
    }

    function bid(uint256 loanId, uint256 amount) external {
        Auction storage auction = auctions[loanId];

        // No self-bidding prevention
        require(block.timestamp < auction.startTime + auction.duration, "Ended");

        auction.highestBid = amount;
        auction.highestBidder = msg.sender;
    }

    function settle(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Off-by-one: uses > not >=
        require(block.timestamp > auction.startTime + auction.duration, "Active");

        // Transfer to highest bidder
    }
}
```

### FIXED
```solidity
contract CompleteFixedAuction {
    AggregatorV3Interface public sequencerUptimeFeed;
    uint256 public constant MIN_DURATION = 1 hours;
    uint256 public constant MAX_DURATION = 7 days;
    uint256 public constant GRACE_PERIOD = 1 hours;

    struct Auction {
        address borrower;
        uint256 startTime;
        uint256 duration;
        uint256 highestBid;
        address highestBidder;
    }

    mapping(uint256 => Auction) public auctions;

    function startAuction(uint256 loanId, uint256 duration) external {
        // L2 sequencer check
        if (address(sequencerUptimeFeed) != address(0)) {
            (, int256 answer, uint256 startedAt, , ) = sequencerUptimeFeed.latestRoundData();
            require(answer == 0, "Sequencer down");
            require(
                block.timestamp >= startedAt + GRACE_PERIOD,
                "Grace period"
            );
        }

        // Duration bounds
        require(duration >= MIN_DURATION, "Too short");
        require(duration <= MAX_DURATION, "Too long");

        auctions[loanId] = Auction({
            borrower: msg.sender,
            startTime: block.timestamp,
            duration: duration,
            highestBid: 0,
            highestBidder: address(0)
        });
    }

    function bid(uint256 loanId, uint256 amount) external {
        Auction storage auction = auctions[loanId];

        // Prevent self-bidding
        require(msg.sender != auction.borrower, "No self-bid");

        // Proper timestamp check
        require(block.timestamp < auction.startTime + auction.duration, "Ended");
        require(amount > auction.highestBid, "Bid too low");

        auction.highestBid = amount;
        auction.highestBidder = msg.sender;
    }

    function settle(uint256 loanId) external {
        Auction storage auction = auctions[loanId];

        // Correct comparison (no off-by-one)
        require(
            block.timestamp > auction.startTime + auction.duration,
            "Active"
        );

        // Transfer to highest bidder
    }
}
```

## Summary: Key Protections

1. **Self-bidding prevention:** Borrower cannot bid on own auction
2. **Sequencer checks:** Validate uptime + grace period on L2
3. **Duration bounds:** Minimum 1 hour, maximum 7 days
4. **Correct comparisons:** bid uses <, settle uses > (no overlap)

## Timestamp Comparison Reference

```solidity
// Correct pattern:
function canBid() public view returns (bool) {
    return block.timestamp < endTime; // Strict before
}

function canSettle() public view returns (bool) {
    return block.timestamp > endTime; // Strict after
}

// At T = endTime:
// canBid() = false
// canSettle() = false
// (1-second gap is acceptable)

// At T = endTime + 1:
// canBid() = false
// canSettle() = true
```

## reference.md

# Auction Manipulation Vulnerability Patterns

## Pattern #1: Self-Bidding to Reset Auction
**Risk:** Borrower can bid on own loan/auction to reset auction timer, extending indefinitely to avoid liquidation
**Detection:** Check if auction allows bidder to be borrower, or if winning bid resets timer
**Impact:** Borrower avoids liquidation indefinitely by repeatedly self-bidding, protocol accumulates bad debt

## Pattern #2: Auction Start During Sequencer Downtime
**Risk:** On L2 chains, auctions starting during sequencer downtime give unfair advantage once sequencer restarts
**Detection:** Verify sequencer uptime checked before auction start on L2 deployments
**Impact:** Unfair auction conditions, first bidder after restart has monopoly during catch-up period

## Pattern #3: Insufficient Auction Length Validation
**Risk:** No minimum auction length allows creating 1-second auctions for immediate seizure bypassing competitive bidding
**Detection:** Check if auction length has minimum bound (e.g., 1 hour)
**Impact:** Liquidator can seize collateral immediately without competitive bidding, borrower gets unfair price

## Pattern #4: Auction Can Be Seized During Active Period
**Risk:** Off-by-one error in timestamp check (using > instead of >=) allows seizure at exact auction end time
**Detection:** Verify auction end check uses >= not >, ensuring grace period respected
**Impact:** Liquidator seizes collateral during auction period before bidders can respond

## templates

```

```

## templates/report-template.md

# Auction Manipulation Security Audit Report

## Executive Summary

**Contract:** [Contract Name]
**Audit Date:** [Date]
**Auditor:** [Name/Team]

**Findings Overview:**
- Critical: X
- High: X
- Medium: X
- Low: X

## Findings

---

### [SEVERITY] Finding #X: [Vulnerability Title]

**Pattern:** #X - [Pattern Name from reference.md]

**Location:** `[contract_name.sol:line_numbers]`

**Description:**

[Detailed explanation of the auction manipulation vulnerability]

**Vulnerable Code:**

```solidity
function bid(uint256 auctionId) external {
    // Missing: self-bid prevention, duration validation, etc.
    auctions[auctionId].highestBidder = msg.sender;
}
```

**Manipulation Scenario:**

[Analyze exploitation:]
- **Attack vector:** [Self-bidding, timing exploit, etc.]
- **Auction impact:** [How auction fairness compromised]
- **Borrower benefit:** [How borrower avoids liquidation]
- **Protocol loss:** [Bad debt accumulation]

**Proof of Concept:**

```solidity
contract AuctionExploit {
    function attack(uint256 loanId) external {
        // 1. Auction starts for underwater loan
        // 2. Attacker (borrower) self-bids
        // 3. Auction ends, attacker "buys" own loan
        // 4. Repeat via refinancing
        // 5. Extend underwater position indefinitely
    }
}
```

**Attack Timeline:**
1. [T+0: Auction starts - loan underwater by $X]
2. [T+12h: Legitimate bidder bids $Y]
3. [T+23h: Borrower self-bids $Y+1]
4. [T+24h: Auction ends, borrower "wins"]
5. [Result: Loan extended, no liquidation, bad debt grows]

**Impact Analysis:**

**Direct Impact:**
- [Liquidation avoidance - borrower extends underwater loan]
- [Bad debt growth - protocol absorbs losses]

**Systemic Impact:**
- [Repeated exploitation - all auctions manipulable]
- [Protocol insolvency - accumulated bad debt]

**Affected Auctions:**
- [Quantify - e.g., "All liquidation auctions vulnerable"]

**Remediation:**

```solidity
function bid(uint256 auctionId, uint256 amount) external {
    Auction storage auction = auctions[auctionId];

    // Prevent self-bidding
    require(msg.sender != auction.borrower, "Cannot self-bid");

    // Validate timing
    require(
        block.timestamp < auction.startTime + auction.duration,
        "Auction ended"
    );

    require(amount > auction.highestBid, "Bid too low");

    auction.highestBid = amount;
    auction.highestBidder = msg.sender;
}
```

**Recommendations:**
1. [Primary fix - e.g., "Prevent borrower from bidding on own auction"]
2. [Timing fix - e.g., "Enforce minimum 1-hour auction duration"]
3. [L2 fix - e.g., "Add sequencer uptime check before auction start"]

**Gas Impact:** [Estimated additional gas]
- Self-bid check: ~100 gas
- Sequencer check: ~20,000 gas

---

### [SEVERITY] Finding #X: [Next Vulnerability]

[Repeat above structure for each finding]

---

## Severity Definitions

**Critical:** Self-bidding allowing infinite auction extensions, off-by-one enabling immediate seizure bypassing auction.

**High:** Insufficient length validation allowing very short auctions (1 second), sequencer downtime affecting L2 auction fairness.

**Medium:** Suboptimal auction parameters reducing competitive bidding, missing events for state changes.

**Low:** Gas inefficiencies in auction logic, missing view functions for transparency.

## Recommendations Summary

### Immediate Actions (Critical/High)
1. [List critical fixes]
   - Example: "Prevent borrower from bidding on own auction"
   - Example: "Enforce minimum auction duration of 1 hour"
   - Example: "Fix off-by-one in timestamp comparison (use > not >=)"

### Short-term Improvements (Medium)
1. [List medium-priority enhancements]
   - Example: "Add sequencer uptime validation on L2"
   - Example: "Implement maximum auction duration bounds"

### Long-term Enhancements (Low)
1. [List optimization opportunities]
   - Example: "Add auction analytics and monitoring"

## Checklist Results

Based on `checklist.md`:

- [x] **No self-bidding:** Borrower cannot bid on own auction ✓/✗
- [x] **Sequencer check:** Uptime validated before start on L2 ✓/✗
- [x] **Minimum length:** Auction duration >= 1 hour enforced ✓/✗
- [x] **Correct comparisons:** Timestamp checks correct (no off-by-one) ✓/✗

## Auction Configuration

- **Minimum duration:** [X hours or "None" ❌]
- **Maximum duration:** [X days or "None" ❌]
- **Self-bidding prevention:** [Yes ✓ / No ❌]
- **L2 sequencer check:** [Yes ✓ / No ❌ / N/A (L1)]

## Auction Timing Analysis

### Self-Bid Exploitation

```
Scenario: Underwater loan, auction starts

T+0h:    Auction begins (borrower owes $100k, collateral worth $80k)
T+12h:   Legitimate bidder: $85k
T+20h:   Legitimate bidder: $88k
T+23h:   Borrower self-bids: $89k
T+24h:   Auction ends, borrower "wins"

Result:
- Borrower pays $89k to themselves
- Loan refinanced with new auction
- Can repeat indefinitely
- Protocol never liquidates, bad debt grows
```

### Off-By-One Timing

```
Auction: startTime=1000, duration=3600
endTime: 4600

At timestamp 4600:
  Vulnerable (>):  4600 > 4600 = FALSE (cannot seize)
  Fixed (>=):      4600 >= 4600 = TRUE (can seize)

But bidding:
  Vulnerable (<):  4600 < 4600 = FALSE (cannot bid)
  Fixed (<):       4600 < 4600 = FALSE (cannot bid)

Issue: At exact boundary (4600):
  - Bid rejected (correct)
  - Seize rejected (vulnerable) or allowed (fixed)

Best practice: Use strict inequalities (< for bid, > for seize)
Creates 1-second gap at boundary, acceptable for fairness
```

### L2 Sequencer Downtime

```
Scenario: Arbitrum sequencer down 2 hours

T+0h:    Auction tx submitted (sequencer down)
T+2h:    Sequencer restarts
T+2h:    Auction tx executes with startTime = T+2h (current time)
T+2h:    Duration = 24h, so endTime = T+26h

Problem: First bidder after restart has full 24h
But if sequencer was down, price may have moved significantly
First bidder has informational advantage

Fix: Reject auction start if sequencer recently restarted
Grace period (e.g., 1h) allows market to stabilize
```

## Testing Recommendations

### Unit Tests
- [ ] Self-bidding prevention (borrower bid reverts)
- [ ] Minimum duration enforcement
- [ ] Maximum duration enforcement
- [ ] Timestamp comparison correctness (bid/settle boundaries)
- [ ] L2 sequencer check (if applicable)

### Integration Tests
- [ ] Full auction lifecycle with multiple bidders
- [ ] Auction during sequencer downtime scenario (L2)
- [ ] Edge case timing (exact boundary timestamps)
- [ ] Refinancing after auction settlement

### Scenario Tests
- [ ] Borrower attempts repeated self-bidding
- [ ] 1-second auction immediate seizure attempt
- [ ] Off-by-one exploitation at exact endTime
- [ ] Sequencer restart during active auction

## Appendix

### Correct Timestamp Patterns

```solidity
// Pattern 1: Strict inequalities (recommended)
function canBid() public view returns (bool) {
    return block.timestamp < auctionEnd;
}

function canSettle() public view returns (bool) {
    return block.timestamp > auctionEnd;
}

// At T = auctionEnd:
// canBid = false, canSettle = false (1-second gap)

// Pattern 2: Allow settle at exact end
function canSettle() public view returns (bool) {
    return block.timestamp >= auctionEnd;
}

// At T = auctionEnd:
// canBid = false, canSettle = true (no gap)
// Risk: Race condition at exact timestamp
```

### L2 Sequencer Integration

```solidity
// Arbitrum Sequencer Feed: 0xFdB631F5EE196F0ed6FAa767959853A9F217697D
// Optimism Sequencer Feed: 0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389

AggregatorV3Interface sequencerFeed = AggregatorV3Interface(feedAddress);

function startAuction() external {
    (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    ) = sequencerFeed.latestRoundData();

    // answer = 0: sequencer up
    // answer = 1: sequencer down
    require(answer == 0, "Sequencer down");

    // Grace period after restart
    uint256 timeSinceUp = block.timestamp - startedAt;
    require(timeSinceUp >= GRACE_PERIOD, "Grace period active");

    // Safe to start auction
}
```

### Auction Duration Standards

| Auction Type | Min Duration | Max Duration | Rationale |
|--------------|-------------|--------------|-----------|
| Liquidation | 1 hour | 7 days | Competitive bidding |
| Loan Sale | 24 hours | 30 days | Price discovery |
| NFT Auction | 1 hour | 14 days | Market depth |

### Self-Bidding Detection

```solidity
// Method 1: Direct check
require(msg.sender != auction.borrower, "No self-bid");

// Method 2: Related party check
mapping(address => address[]) public relatedParties;
for (uint i = 0; i < relatedParties[auction.borrower].length; i++) {
    require(msg.sender != relatedParties[auction.borrower][i], "Related party");
}

// Method 3: Economic check
// Ensure bid comes from external wallet with independent funds
require(msg.sender != auction.borrower, "No self-bid");
require(msg.sender.code.length == 0, "No contract bids");
```

### Timeline Validation

```solidity
// Ensure auction progresses through valid states
enum AuctionState { None, Active, Ended, Settled }

function getState(uint256 auctionId) public view returns (AuctionState) {
    Auction storage auction = auctions[auctionId];

    if (auction.startTime == 0) return AuctionState.None;

    if (block.timestamp <= auction.startTime + auction.duration) {
        return AuctionState.Active;
    }

    if (!auction.settled) return AuctionState.Ended;

    return AuctionState.Settled;
}

// Enforce state transitions
modifier onlyState(uint256 auctionId, AuctionState requiredState) {
    require(getState(auctionId) == requiredState, "Invalid state");
    _;
}
```

