Skip to content

AuctionHouse

Dutch auction for seized collateral from insolvent CDP positions. Sells collateral for TARE; proceeds route to engine via absorb_auction_proceeds.

Source: TARE-Stablecoin/src/auction_house.vy (Vyper 0.4.3)

Concept guide: Liquidations protocol page

Wired by: TareEngine start_auction · set_auction_house

Implementation overview

  • Engine-only lots — only ENGINE can open_lot; collateral already seized via settle_bad_debt.
  • Dutch pricing — ask decays from top (oracle × premium) to floor over duration.
  • Partial fills — buyers call take at current price until lot cleared.
  • Proceeds — TARE forwarded to engine.absorb_auction_proceeds (surplus refill → bad-debt burn).
flowchart LR
    Engine[TareEngine] -->|seized collateral| AH[AuctionHouse]
    Buyer[Buyer] -->|TARE| AH
    AH -->|absorb_auction_proceeds| Engine

Price curve:

top   = oracle_unit_price × start_premium / 1e18
floor = top × floor_pct / 1e18
price(t) = top - (top - floor) × min(elapsed, duration) / duration

Immutables

Name Role
ENGINE TareEngine — only caller for open_lot
TARE Payment token

Events

Event When
LotOpened New auction lot
LotTaken Buyer purchases collateral slice
LotClosed Lot fully cleared
LotRestarted Owner resets stale lot pricing
StartPremiumSet / FloorPctSet / DurationSet Parameter updates
Paused Circuit breaker

Engine integration

open_lot

AuctionHouse.open_lot(token, amount, token_decimals)

Open Dutch auction lot for seized collateral already held by this contract. Engine transfers collateral before calling.

Param Type Description
token address Seized collateral ERC-20
amount uint256 Lot size (token's native decimals)
token_decimals uint256 Cached decimals for take pricing math

Returns / state: Allocates lot_id = next_lot_id, increments counter. Snapshots top (oracle × start_premium) and floor (top × floor_pct), sets start_time = block.timestamp, active = True.

Access: ENGINE immutable only. Reverts if paused or amount == 0.

Events: LotOpened(lot_id, token, amount, top, floor).

Source (TARE-Stablecoin/src/auction_house.vy:170-197):

@external
@nonreentrant
def open_lot(token: address, amount: uint256, token_decimals: uint256):
    assert msg.sender == ENGINE, "AuctionHouse: only engine"
    assert not self.paused, "AuctionHouse: paused"
    assert amount > 0, "AuctionHouse: zero amount"
    top: uint256 = self._oracle_top(token, token_decimals)
    floor_price: uint256 = top * self.floor_pct // PRECISION
    lot_id: uint256 = self.next_lot_id
    self.next_lot_id = lot_id + 1
    self.lots[lot_id] = Lot(...)
    log LotOpened(lot_id=lot_id, token=token, amount=amount, top=top, floor=floor_price)

Example:

# Called by TareEngine.start_auction after transfer — not user-callable
auction_house.open_lot(weth, 5 * 10**18, 18)

Cross-links: TareEngine start_auction · take · current_price

Buyer

take

AuctionHouse.take(lot_id, max_collateral, max_tare)

Buy collateral from live lot at current Dutch price. Partial fills allowed.

Param Type Description
lot_id uint256 Lot to purchase from
max_collateral uint256 Max collateral to receive (token decimals)
max_tare uint256 Max TARE to spend (slippage cap)

Returns / state: Shrinks lots[lot_id].remaining; deactivates lot when cleared. Transfers TARE from buyer → contract → engine.absorb_auction_proceeds; sends collateral to buyer.

Access: Any caller when not paused. Buyer must approve TARE on this contract.

Events: LotTaken(lot_id, buyer, collateral_out, tare_paid, price); LotClosed when remaining hits zero.

Source (TARE-Stablecoin/src/auction_house.vy:200-251):

@external
@nonreentrant
def take(lot_id: uint256, max_collateral: uint256, max_tare: uint256):
    price: uint256 = self._price(lot)
    collateral_out: uint256 = min(max_collateral, lot.remaining)
    pay: uint256 = collateral_out * price // unit
    if pay > max_tare:
        collateral_out = max_tare * unit // price
        ...
    extcall TARE.transferFrom(msg.sender, self, pay)
    extcall TARE.approve(ENGINE, pay)
    extcall IEngine(ENGINE).absorb_auction_proceeds(pay)
    extcall IERC20(lot.token).transfer(msg.sender, collateral_out)

Example:

tare.approve(auction_house, 10_000 * 10**18)
auction_house.take(lot_id=0, max_collateral=2 * 10**18, max_tare=5_000 * 10**18)

Cross-links: open_lot · current_price · TareEngine absorb_auction_proceeds

Admin

restart_lot

AuctionHouse.restart_lot(lot_id)

Re-anchor stale lot to current oracle price and reset decay window. Remaining collateral unchanged.

Param Type Description
lot_id uint256 Active lot that floored without clearing

Returns / state: Recomputes top / floor from live oracle + current start_premium / floor_pct; sets start_time = block.timestamp.

Access: owner only. Lot must still be active.

Events: LotRestarted(lot_id, top, floor).

Source (TARE-Stablecoin/src/auction_house.vy:258-273):

@external
def restart_lot(lot_id: uint256):
    ow._check_owner()
    lot: Lot = self.lots[lot_id]
    assert lot.active, "AuctionHouse: lot not active"
    top: uint256 = self._oracle_top(lot.token, lot.token_decimals)
    floor_price: uint256 = top * self.floor_pct // PRECISION
    self.lots[lot_id].top = top
    self.lots[lot_id].floor = floor_price
    self.lots[lot_id].start_time = block.timestamp
    log LotRestarted(lot_id=lot_id, top=top, floor=floor_price)

Example:

auction_house.restart_lot(3)  # owner — reprices lot 3 from fresh oracle

Cross-links: open_lot · take · set_start_premium

set_start_premium

AuctionHouse.set_start_premium(premium)

Set oracle premium multiplier for new lots and restart_lot repricing. Live lots keep their snapshot.

Param Type Description
premium uint256 1e18-scaled multiplier on oracle unit price (MIN_PREMIUMMAX_PREMIUM, i.e. 1.0x–2.0x)

Returns / state: Updates start_premium storage. Default at deploy: 1.1e18 (10% over oracle).

Access: owner only.

Events: StartPremiumSet(premium).

Source (TARE-Stablecoin/src/auction_house.vy:276-281):

@external
def set_start_premium(premium: uint256):
    ow._check_owner()
    assert premium >= MIN_PREMIUM and premium <= MAX_PREMIUM, "AuctionHouse: premium bounds"
    self.start_premium = premium
    log StartPremiumSet(premium=premium)

Example:

auction_house.set_start_premium(12 * 10**17)  # 1.2x oracle

Cross-links: open_lot · restart_lot · set_floor_pct

set_floor_pct

AuctionHouse.set_floor_pct(floor_pct_)

Set floor as fraction of top for new lots and restarts. floor = top × floor_pct / 1e18.

Param Type Description
floor_pct_ uint256 1e18-scaled fraction of top at end of decay (0 < floor_pct_ < 1e18)

Returns / state: Updates floor_pct storage. Default at deploy: 0.5e18 (50% of top).

Access: owner only.

Events: FloorPctSet(floor_pct).

Source (TARE-Stablecoin/src/auction_house.vy:284-289):

@external
def set_floor_pct(floor_pct_: uint256):
    ow._check_owner()
    assert floor_pct_ > 0 and floor_pct_ < PRECISION, "AuctionHouse: floor bounds"
    self.floor_pct = floor_pct_
    log FloorPctSet(floor_pct=floor_pct_)

Example:

auction_house.set_floor_pct(6 * 10**17)  # floor at 60% of top

Cross-links: set_start_premium · set_duration · open_lot

set_duration

AuctionHouse.set_duration(duration_)

Set seconds for full top→floor Dutch decay on new lots and restarts.

Param Type Description
duration_ uint256 Decay window in seconds (MIN_DURATIONMAX_DURATION, 60s–1 week)

Returns / state: Updates duration storage. Default at deploy: 3600 (1 hour).

Access: owner only.

Events: DurationSet(duration).

Source (TARE-Stablecoin/src/auction_house.vy:292-297):

@external
def set_duration(duration_: uint256):
    ow._check_owner()
    assert duration_ >= MIN_DURATION and duration_ <= MAX_DURATION, "AuctionHouse: duration bounds"
    self.duration = duration_
    log DurationSet(duration=duration_)

Example:

auction_house.set_duration(2 * 60 * 60)  # 2-hour decay window

Cross-links: set_floor_pct · open_lot · current_price

pause

AuctionHouse.pause()

Circuit breaker — blocks open_lot and take. Views still callable.

Returns / state: Sets paused = True.

Access: owner only.

Events: Paused(status=True).

Source (TARE-Stablecoin/src/auction_house.vy:300-304):

@external
def pause():
    ow._check_owner()
    self.paused = True
    log Paused(status=True)

Example:

auction_house.pause()

Cross-links: unpause · open_lot · take

unpause

AuctionHouse.unpause()

Resume open_lot and take after owner pause.

Returns / state: Sets paused = False.

Access: owner only.

Events: Paused(status=False).

Source (TARE-Stablecoin/src/auction_house.vy:307-311):

@external
def unpause():
    ow._check_owner()
    self.paused = False
    log Paused(status=False)

Example:

auction_house.unpause()

Cross-links: pause · open_lot · take

Views

current_price

AuctionHouse.current_price(lot_id) -> uint256

Current TARE-per-whole-token ask for active lot (Dutch decay in progress).

Param Type Description
lot_id uint256 Lot to quote

Returns: uint256 — price in 1e18-scaled TARE per whole collateral token. At/after duration, returns floor.

Access: view, any caller. Reverts if lot not active.

Source (TARE-Stablecoin/src/auction_house.vy:318-324, _price:340-347):

@external
@view
def current_price(lot_id: uint256) -> uint256:
    lot: Lot = self.lots[lot_id]
    assert lot.active, "AuctionHouse: lot not active"
    return self._price(lot)

Example:

price = auction_house.current_price(0)  # TARE per 1 whole token

Cross-links: take · open_lot · set_duration