;; @contract Auction Engine - Sells off vault collateral to raise USDA
;; @version 3

(impl-trait .arkadiko-auction-engine-trait-v2.auction-engine-trait)
(use-trait vault-trait .arkadiko-vault-trait-v1.vault-trait)
(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
(use-trait vault-manager-trait .arkadiko-vault-manager-trait-v1.vault-manager-trait)
(use-trait oracle-trait .arkadiko-oracle-trait-v1.oracle-trait)
(use-trait auction-engine-trait .arkadiko-auction-engine-trait-v2.auction-engine-trait)
(use-trait collateral-types-trait .arkadiko-collateral-types-trait-v1.collateral-types-trait)

;; errors
(define-constant ERR-LOT-NOT-OPEN u21)
(define-constant ERR-LOT-SOLD u22)
(define-constant ERR-POOR-BID u23)
(define-constant ERR-AUCTION-NOT-ALLOWED u25)
(define-constant ERR-NOT-AUTHORIZED u2403)
(define-constant ERR-AUCTION-NOT-OPEN u28)
(define-constant ERR-BLOCK-HEIGHT-NOT-REACHED u29)
(define-constant ERR-AUCTION-NOT-CLOSED u210)
(define-constant ERR-LOT-ALREADY-REDEEMED u211)
(define-constant ERR-TOKEN-TYPE-MISMATCH u212)
(define-constant ERR-EMERGENCY-SHUTDOWN-ACTIVATED u213)

(define-constant blocks-per-day u144)

(define-map auctions
  { id: uint }
  {
    id: uint,
    auction-type: (string-ascii 64),
    collateral-amount: uint,
    collateral-token: (string-ascii 12),
    collateral-address: principal,
    debt-to-raise: uint,
    discount: uint,
    vault-id: uint,
    lot-size: uint,
    lots-sold: uint,
    total-collateral-sold: uint,
    total-debt-raised: uint,
    total-debt-burned: uint,
    ends-at: uint
  }
)
(define-map bids
  { auction-id: uint, lot-index: uint }
  {
    usda: uint,
    collateral-amount: uint,
    collateral-token: (string-ascii 12),
    collateral-address: principal,
    owner: principal,
    redeemed: bool
  }
)

(define-map winning-lots
  { user: principal }
  { ids: (list 100 (tuple (auction-id uint) (lot-index uint))) }
)
(define-map redeeming-lot
  { auction-id: uint, lot-index: uint }
  { bidder: principal }
)

(define-data-var last-auction-id uint u0)
(define-data-var auction-ids (list 1000 uint) (list u0))
(define-data-var lot-size uint u1000000000) ;; 1000 USDA
(define-data-var auction-engine-shutdown-activated bool false)
(define-data-var removing-auction-id uint u0)

(define-read-only (get-auction-by-id (id uint))
  (default-to
    {
      id: u0,
      auction-type: "collateral",
      collateral-amount: u0,
      collateral-token: "",
      collateral-address: (contract-call? .arkadiko-dao get-dao-owner),
      debt-to-raise: u0,
      discount: u0,
      vault-id: u0,
      lot-size: u0,
      lots-sold: u0,
      total-collateral-sold: u0,
      total-debt-raised: u0,
      total-debt-burned: u0,
      ends-at: u0,
    }
    (map-get? auctions { id: id })
  )
)

;; @desc get all auctions that are currently open
(define-read-only (get-auctions)
  (ok (map get-auction-by-id (var-get auction-ids)))
)

;; @desc get all auction IDs of auctions that are currently open
(define-read-only (get-auction-ids)
  (ok (var-get auction-ids))
)

;; @desc get the last bid on certain a lot of an auction 
;; @param aution-id; ID of the auction
;; @param lot-index; index of the lot in the auction
(define-read-only (get-last-bid (auction-id uint) (lot-index uint))
  (default-to
    {
      usda: u0,
      collateral-amount: u0,
      collateral-token: "",
      collateral-address: (contract-call? .arkadiko-dao get-dao-owner),
      owner: (contract-call? .arkadiko-dao get-dao-owner),
      redeemed: false
    }
    (map-get? bids { auction-id: auction-id, lot-index: lot-index })
  )
)

;; @desc get all winning lots for a principal
;; @param owner; principal of the lot winner
(define-read-only (get-winning-lots (owner principal))
  (default-to
    { ids: (list (tuple (auction-id u0) (lot-index u0))) }
    (map-get? winning-lots { user: owner })
  )
)

;; @desc check if auction open (not enough debt raised + end block height not reached)
;; @param auction-id; ID of the auction to be checked
(define-read-only (get-auction-open (auction-id uint))
  (let (
    (auction (get-auction-by-id auction-id))
  )
    (if
      (or
        (>= block-height (get ends-at auction))
        (>= (get total-debt-raised auction) (get debt-to-raise auction))
      )
      (ok false)
      (ok true)
    )
  )
)

;; @desc toggles the killswitch of the auction engine
(define-public (toggle-auction-engine-shutdown)
  (begin
    (asserts! (is-eq tx-sender (contract-call? .arkadiko-dao get-guardian-address)) (err ERR-NOT-AUTHORIZED))

    (ok (var-set auction-engine-shutdown-activated (not (var-get auction-engine-shutdown-activated))))
  )
)


;; @desc ;; 1. Create auction object in map per 100 USDA
;; 2. Add auction ID to list (to show in UI)
;; we want to sell as little collateral as possible to cover the vault's debt
;; if we cannot cover the vault's debt with the collateral sale,
;; we will have to sell some governance or STX tokens from the reserve
;; @param vault-id; ID of the vault that got liquidated for which an auction needs to be started
;; @param uamount; the total amount of collateral that can be sold off in the auction
;; @param extra-debt; the extra debt penalty that has to be raised on top of the vault debt
;; @param vault-debt; the debt in the vault that is now considered bad debt since it is not sufficiently collateralised
;; @param discount; the discount to be given in an auction on the collateral to keepers
;; @post auction; a new auction will be created that runs for one day
(define-public (start-auction
  (vault-id uint)
  (coll-type <collateral-types-trait>)
  (uamount uint)
  (extra-debt uint)
  (vault-debt uint)
  (discount uint)
)
  (let ((vault (contract-call? .arkadiko-vault-data-v1-1 get-vault-by-id vault-id)))
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "liquidator"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) true) (err ERR-AUCTION-NOT-ALLOWED))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (let (
      (auction-id (+ (var-get last-auction-id) u1))
      (auction {
        id: auction-id,
        auction-type: "collateral",
        collateral-amount: uamount,
        collateral-token: (get collateral-token vault),
        collateral-address: (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))),
        debt-to-raise: (+ extra-debt vault-debt),
        discount: discount,
        vault-id: vault-id,
        lot-size: (var-get lot-size),
        lots-sold: u0,
        ends-at: (+ block-height blocks-per-day),
        total-collateral-sold: u0,
        total-debt-raised: u0,
        total-debt-burned: u0
      })
    )
      (map-set auctions { id: auction-id } auction )
      (var-set auction-ids (unwrap-panic (as-max-len? (append (var-get auction-ids) auction-id) u1000)))
      (var-set last-auction-id auction-id)
      (print { type: "auction", action: "created", data: auction })
      (ok true)
    )
  )
)




;; @desc start an auction to sell off DIKO governance tokens
;; this is a private function since it should only be called
;; when a normal collateral liquidation auction can't raise enough debt to cover the vault's debt
;; @param vault-id; ID of the vault for which not enough USDA could be raised
;; @param debt-to-raise; the amount of debt left to raise after running the initial collateral auction
;; @param discount; the discount on the DIKO collateral to the keepers
;; @post auction; a new auction will be created that runs for one day
(define-private (start-debt-auction (vault-id uint) (debt-to-raise uint) (discount uint))
  (let ((vault (contract-call? .arkadiko-vault-data-v1-1 get-vault-by-id vault-id)))
    (asserts! (is-eq (get is-liquidated vault) true) (err ERR-AUCTION-NOT-ALLOWED))

    (let (
      (auction-id (+ (var-get last-auction-id) u1))
      (auction {
        id: auction-id,
        auction-type: "debt",
        collateral-amount: (* debt-to-raise u1000000),
        collateral-token: "DIKO",
        collateral-address: .arkadiko-token,
        debt-to-raise: debt-to-raise,
        discount: discount,
        vault-id: vault-id,
        lot-size: (var-get lot-size),
        lots-sold: u0,
        ends-at: (+ block-height blocks-per-day),
        total-collateral-sold: u0,
        total-debt-raised: u0,
        total-debt-burned: u0
      })
    )
      (map-set auctions { id: auction-id } auction)
      (var-set auction-ids (unwrap-panic (as-max-len? (append (var-get auction-ids) auction-id) u1000)))
      (var-set last-auction-id auction-id)
      (print { type: "auction", action: "created", data: auction })
    )
    (ok true)
  )
)

;; @desc calculate the discounted auction price on the (dollarcent) price of the collateral
;; @param price; the current on-chain price
;; @param auction-id; the ID of the auction in which the collateral will be sold
(define-read-only (discounted-auction-price (price uint) (decimals uint) (auction-id uint))
  (let (
    (auction (get-auction-by-id auction-id))
    (discount (* price (get discount auction)))
    (total-decimals (if (> decimals u0)
      decimals
      u1000000
    ))
  )
    (ok (/ (- (* u100 price) discount) total-decimals))
  )
)

;; @desc calculates the minimum collateral amount to sell
;; e.g. if we need to cover 10 USDA debt, and we have 20 STX at $1/STX,
;; we only need to auction off 10 STX excluding an additional discount
;; @param oracle; the oracle implementation that provides the on-chain price
;; @param auction-id; the ID of the auction for which the collateral amount should be calculated
(define-public (get-minimum-collateral-amount (oracle <oracle-trait>) (auction-id uint))
  (let (
    (auction (get-auction-by-id auction-id))
    (collateral-amount-auction (get collateral-amount auction))
    (collateral-sold (get total-collateral-sold auction))
    (collateral-left
      (if (> collateral-amount-auction collateral-sold)
        (- collateral-amount-auction collateral-sold)
        u0
      )
    )
    (debt-to-raise (get debt-to-raise auction))
    (total-debt-raised (get total-debt-raised auction))
    (debt-left-to-raise
      (if (> debt-to-raise total-debt-raised)
        (- debt-to-raise total-debt-raised)
        u0
      )
    )
    (collateral-price (unwrap-panic (contract-call? oracle fetch-price (collateral-token (get collateral-token auction)))))
    (discounted-price (unwrap-panic (discounted-auction-price (get last-price collateral-price) (get decimals collateral-price) auction-id)))
  )
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (if (< debt-left-to-raise (get lot-size auction))
      (let ((collateral-amount (/ (* u100 debt-left-to-raise) discounted-price)))
        (if (> collateral-amount collateral-left)
          (ok collateral-left)
          (ok collateral-amount)
        )
      )
      (let ((collateral-amount (/ (* u100 (get lot-size auction)) discounted-price)))
        (if (> collateral-amount collateral-left)
          (ok collateral-left)
          (ok collateral-amount)
        )
      )
    )
  )
)

;; @desc bid a certain USDA amount on a lot in an auction
;; @param vault-manager; the contract that manages the vault for which the collateral is sold off
;; @param oracle; the oracle that provides on-chain prices for the collateral that is sold off
;; @param coll-type; the contract that implements the parameters of the collateral types compatible with Arkadiko vaults
;; @param auction-id; the ID of the auction that is bid on
;; @param lot-index; the ID of the lot that is bid on in the auction
;; @param usda; the amount of USDA that is bid. If an amount equal to the lot size (usually 1000 USDA) is bid, the lot is automatically won
;; @post usda; the usda amount will be transferred from the tx-sender to this contract
;; @post bid; the lot will be assigned to the current bidder
;; @post stacker-payout; if the STX are being stacked, the yield will go to the keeper pro-rata to the amount of USDA the keeper bought
(define-public (bid
  (vault-manager <vault-manager-trait>)
  (oracle <oracle-trait>)
  (coll-type <collateral-types-trait>)
  (auction-id uint)
  (lot-index uint)
  (usda uint)
)
  (let ((auction (get-auction-by-id auction-id)))
    (asserts! (is-eq lot-index (get lots-sold auction)) (err ERR-LOT-NOT-OPEN))
    (asserts! (is-eq (unwrap-panic (get-auction-open auction-id)) true) (err ERR-AUCTION-NOT-OPEN))
    (asserts! (is-eq (contract-of vault-manager) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "freddie"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (register-bid vault-manager oracle coll-type auction-id lot-index usda)
  )
)

;; @desc allows lot bidders to redeem the collateral they won after an auction has closed
;; @param vault-manager; the contract that manages the vault for which the collateral is sold off
;; @param ft; the fungible token that was sold off (either a SIP10 token or STX)
;; @param reserve; the reserve that stores the fungible tokens (collateral) that were sold off
;; @param coll-type; the contract that implements the parameters of the collateral types compatible with Arkadiko vaults
;; @param auction-id; the ID of the auction that from which the collateral will be redeemed
;; @param lot-index; the ID of the lot that was won and will now be redeemed
;; @post stx/sip10; will be transferred to the tx-sender if they won the lot
(define-public (redeem-lot-collateral
  (vault-manager <vault-manager-trait>)
  (ft <ft-trait>)
  (reserve <vault-trait>)
  (coll-type <collateral-types-trait>)
  (auction-id uint)
  (lot-index uint)
)
  (let (
    (last-bid (get-last-bid auction-id lot-index))
    (auction (get-auction-by-id auction-id))
    (token-address (get collateral-address auction))
    (token-string (get collateral-token auction))
  )
    (asserts!
      (or
        (is-eq (contract-of ft) token-address)
        (is-eq "STX" token-string)
        (is-eq "xSTX" token-string)
      )
      (err ERR-TOKEN-TYPE-MISMATCH)
    )
    (asserts! (is-eq (contract-of vault-manager) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "freddie"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq tx-sender (get owner last-bid)) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (unwrap-panic (get-auction-open auction-id)) false) (err ERR-AUCTION-NOT-CLOSED))
    (asserts! (is-eq (get redeemed last-bid) false) (err ERR-LOT-ALREADY-REDEEMED))

    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    ;; Update bid
    (map-set bids
      { auction-id: auction-id, lot-index: lot-index }
      (merge last-bid {
        redeemed: true
      })
    )

    (if (is-eq (get auction-type auction) "debt")
      ;; request "collateral-amount" gov tokens from the DAO
      (begin
        (try! (contract-call? .arkadiko-dao request-diko-tokens (get collateral-amount last-bid)))
        (try! (contract-call? vault-manager redeem-auction-collateral ft token-string reserve (get collateral-amount last-bid) tx-sender))
      )
      (try! (contract-call? vault-manager redeem-auction-collateral ft token-string reserve (get collateral-amount last-bid) tx-sender))
    )
    (if (< (get usda last-bid) (var-get lot-size))
      (begin
        (map-set auctions
          { id: auction-id }
          (merge auction {
            lots-sold: (+ u1 (get lots-sold auction)),
          })
        )
        (try! (close-auction vault-manager coll-type auction-id))
      )
      false
    )

    (print { type: "lot", action: "redeemed", data: { auction-id: auction-id, lot-index: lot-index } })
    (ok true)
  )
)

;; @desc closes an auction
;; 1. flag auction on map as closed
;; 2. allow person to collect collateral from reserve manually
;; 3. check if vault debt is covered (sum of USDA in lots >= debt-to-raise)
;; 4. update vault to allow vault owner to withdraw leftover collateral (if any)
;; 5. if not all vault debt is covered: auction off collateral again (if any left)
;; 6. if not all vault debt is covered and no collateral is left: cover USDA with gov token
;; @param vault-manager; the contract that manages the vault for which the collateral was sold off
;; @param coll-type; the contract that implements the parameters of the collateral types compatible with Arkadiko vaults
;; @param auction-id: the ID of the auction that should be closed
(define-public (close-auction
  (vault-manager <vault-manager-trait>)
  (coll-type <collateral-types-trait>)
  (auction-id uint)
)
  (let ((auction (get-auction-by-id auction-id)))
    (asserts!
      (or
        (>= block-height (get ends-at auction))
        (>= (get total-debt-raised auction) (get debt-to-raise auction))
      )
      (err ERR-BLOCK-HEIGHT-NOT-REACHED)
    )
    (asserts! (is-eq (unwrap-panic (get-auction-open auction-id)) false) (err ERR-AUCTION-NOT-CLOSED))
    (asserts! (is-eq (contract-of vault-manager) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "freddie"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (let (
      (vault (contract-call? .arkadiko-vault-data-v1-1 get-vault-by-id (get vault-id auction)))
    )
      (if (> (get debt vault) (get total-debt-burned auction))
        (let (
          (amount-to-burn (min-of (- (get total-debt-raised auction) (get total-debt-burned auction)) (- (get debt vault) (get total-debt-burned auction))))
        )
          (if (> amount-to-burn u0)
            (try! (contract-call? .arkadiko-dao burn-token .usda-token amount-to-burn (as-contract tx-sender)))
            true
          )
          (map-set auctions
            { id: auction-id }
            (merge auction { total-debt-burned: (+ (get total-debt-burned auction) amount-to-burn) })
          )
        )
        true
      )
    )
    (try!
      (if (>= (get total-debt-raised auction) (get debt-to-raise auction))
        (begin
          (unwrap-panic (remove-auction auction-id))
          (if (is-eq (get auction-type auction) "collateral")
            (contract-call?
              vault-manager
              finalize-liquidation
              (get vault-id auction)
              (- (get collateral-amount auction) (get total-collateral-sold auction))
              coll-type
            )
            (contract-call?
              vault-manager
              finalize-liquidation
              (get vault-id auction)
              u0
              coll-type
            )
          )
        )
        (if (< (get total-collateral-sold auction) (get collateral-amount auction)) ;; we have some collateral left to auction
          ;; extend auction with collateral that is left
          (extend-auction auction-id)
          (begin
            ;; no collateral left. Need to sell governance token to raise more USDA
            (unwrap-panic (remove-auction auction-id))
            (start-debt-auction
              (get vault-id auction)
              (- (get debt-to-raise auction) (get total-debt-raised auction))
              u0
            )
          )
        )
      )
    )
    (print { type: "auction", action: "closed", data: { auction-id: auction-id } })
    (ok true)
  )
)

;; @desc called when upgrading contracts
;; auction engine should only contain USDA from bids
;; @param auction-engine; the contract of the new auction engine
;; @param token; the token contract that specifies the FT to transfer, usually will be usda-token
;; @post ft; all the tokens will have been transferred to the new contract
(define-public (migrate-funds (auction-engine <auction-engine-trait>) (token <ft-trait>))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .arkadiko-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (let (
      (balance (unwrap-panic (contract-call? token get-balance (as-contract tx-sender))))
    )
      (as-contract (contract-call? token transfer balance tx-sender (contract-of auction-engine) none))
    )
  )
)

;; @desc redeem USDA to burn DIKO gov token from open market
;; taken from auctions, paid by liquidation penalty on vaults
;; @param usda-amount; the amount of USDA to be redeemed from the contract
;; @post usda; all USDA tokens will have been transferred to DAO's payout address
(define-public (redeem-usda (usda-amount uint))
  (begin
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .arkadiko-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get auction-engine-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (as-contract (contract-call? .usda-token transfer usda-amount tx-sender (contract-call? .arkadiko-dao get-payout-address) none))
  )
)


;;;;;;;;;;;;;;;;;;;;;;;
;; private functions ;;
;;;;;;;;;;;;;;;;;;;;;;;

(define-private (collateral-token (token (string-ascii 12)))
  (if (is-eq token "xSTX")
    "STX"
    token
  )
)

(define-private (min-of (i1 uint) (i2 uint))
  (if (< i1 i2)
    i1
    i2
  )
)

(define-private (register-bid
  (vault-manager <vault-manager-trait>)
  (oracle <oracle-trait>)
  (coll-type <collateral-types-trait>)
  (auction-id uint)
  (lot-index uint)
  (usda uint)
)
  (let (
    (auction (get-auction-by-id auction-id))
    (last-bid (get-last-bid auction-id lot-index))
    (collateral-amount (unwrap-panic (get-minimum-collateral-amount oracle auction-id)))
    (lot-got-sold (if (>= usda (var-get lot-size))
        u1
        u0
      )
    )
    (lots (get-winning-lots tx-sender))
  )
    ;; Lot is sold once bid is > lot-size
    (asserts! (< (get usda last-bid) (var-get lot-size)) (err ERR-LOT-SOLD))
    ;; Need a better bid than previously already accepted
    (asserts! (> usda (get usda last-bid)) (err ERR-POOR-BID)) 

    ;; Return USDA of last bid to (now lost) bidder
    (if (> (get usda last-bid) u0)
      (try! (return-usda (get owner last-bid) (get usda last-bid) auction-id lot-index))
      true
    )
    ;; Transfer USDA from liquidator to this contract
    (try! (contract-call? .usda-token transfer usda tx-sender (as-contract tx-sender) none))

    ;; Update auctions
    (map-set auctions
      { id: auction-id }
      (merge auction {
        lots-sold: (+ lot-got-sold (get lots-sold auction)),
        total-collateral-sold: (- (+ collateral-amount (get total-collateral-sold auction)) (get collateral-amount last-bid)),
        total-debt-raised: (- (+ usda (get total-debt-raised auction)) (get usda last-bid))
      })
    )
    ;; Update bids
    (map-set bids
      { auction-id: auction-id, lot-index: lot-index }
      {
        usda: usda,
        collateral-amount: collateral-amount,
        collateral-token: (get collateral-token auction),
        collateral-address: (get collateral-address auction),
        owner: tx-sender,
        redeemed: false
      }
    )
    (map-set winning-lots
      { user: tx-sender }
      {
        ids: (unwrap-panic (as-max-len? (append (get ids lots) (tuple (auction-id auction-id) (lot-index lot-index))) u100))
      }
    )

    ;; Set stacker payout
    (try! (contract-call? .arkadiko-vault-data-v1-1 set-stacker-payout (get vault-id auction) lot-index collateral-amount tx-sender))
    (print { type: "bid", action: "registered", data: { auction-id: auction-id, lot-index: lot-index, usda: usda } })

    ;; End auction if needed
    (if
      (or
        (>= block-height (get ends-at auction))
        (>= (- (+ usda (get total-debt-raised auction)) (get usda last-bid)) (get debt-to-raise auction))
      )
      ;; auction is over - close all bids
      ;; send collateral to winning bidders
      (close-auction vault-manager coll-type auction-id)
      (ok true)
    )
  )
)

(define-private (remove-winning-lot (lot (tuple (auction-id uint) (lot-index uint))))
  (if (is-some (map-get? redeeming-lot { auction-id: (get auction-id lot), lot-index: (get lot-index lot) }))
    false
    true
  )
)

(define-private (return-usda (owner principal) (usda uint) (auction-id uint) (lot-index uint))
  (if (> usda u0)
    (let ((lots (get-winning-lots owner)))
      (map-set redeeming-lot { auction-id: auction-id, lot-index: lot-index } { bidder: owner })
      (map-set winning-lots { user: owner } { ids: (filter remove-winning-lot (get ids lots)) })
      (try! (as-contract (contract-call? .usda-token transfer usda (as-contract tx-sender) owner none)))
      (ok (map-delete redeeming-lot { auction-id: auction-id, lot-index: lot-index }))
    )
    (err u0) ;; don't really care if this fails.
  )
)

(define-private (extend-auction (auction-id uint))
  (let ((auction (get-auction-by-id auction-id)))
    (map-set auctions
      { id: auction-id }
      (merge auction {
        total-debt-burned: (get total-debt-raised auction),
        ends-at: (+ (get ends-at auction) blocks-per-day)
      })
    )
    (ok true)
  )
)

(define-private (remove-auction (auction-id uint))
  (begin
    (var-set removing-auction-id auction-id)
    (var-set auction-ids (unwrap-panic (as-max-len? (filter remove-closed-auction (var-get auction-ids)) u1000)))
    (ok true)
  )
)

(define-private (remove-closed-auction (auction-id uint))
  (if (is-eq auction-id (var-get removing-auction-id))
    false
    true
  )
)
