;; @contract Hettich - The Vault Manager
;; Hettich is an abstraction layer that interacts with collateral type reserves
;; Ideally, collateral reserves should never be called from outside. Only manager layers (such as this one) should be interacted with from clients
;; @version 1

(impl-trait .googlier-vault-manager-trait-v1.vault-manager-trait)
(use-trait vault-trait .googlier-vault-trait-v1.vault-trait)
(use-trait ft-trait .sip-010-trait-ft-standard.sip-010-trait)
(use-trait vault-manager-trait .googlier-vault-manager-trait-v1.vault-manager-trait)
(use-trait collateral-types-trait .googlier-collateral-types-trait-v1.collateral-types-trait)
(use-trait oracle-trait .googlier-oracle-trait-v1.oracle-trait)

;; errors
(define-constant ERR-NOT-AUTHORIZED u4401)
(define-constant ERR-TRANSFER-FAILED u42)
(define-constant ERR-MINTER-FAILED u43)
(define-constant ERR-BURN-FAILED u44)
(define-constant ERR-DEPOSIT-FAILED u45)
(define-constant ERR-WITHDRAW-FAILED u46)
(define-constant ERR-MINT-FAILED u47)
(define-constant ERR-LIQUIDATION-FAILED u48)
(define-constant ERR-INSUFFICIENT-COLLATERAL u49)
(define-constant ERR-MAXIMUM-DEBT-REACHED u410)
(define-constant ERR-EMERGENCY-SHUTDOWN-ACTIVATED u411)
(define-constant ERR-BURN-HEIGHT-NOT-REACHED u412)
(define-constant ERR-VAULT-LIQUIDATED u413)
(define-constant ERR-STACKING-IN-PROGRESS u414)
(define-constant ERR-WRONG-COLLATERAL-TOKEN u415)
(define-constant ERR-VAULT-NOT-LIQUIDATED u416)
(define-constant ERR-WRONG-DEBT u417)
(define-constant ERR-AUCTION-NOT-ENDED u418)

;; constants
(define-constant BLOCKS-PER-DAY u144)

(define-data-var stx-redeemable uint u0) ;; how much STX is available to trade for xSTX
(define-data-var block-height-last-paid uint u0) ;; when the foundation was last paid
(define-data-var maximum-debt-surplus uint u10000000000000) ;; 10 million default - above that we sell the USD on the GGLR/USD pair to burn GGLR
(define-map stacking-unlock-burn-height
  { stacker-name: (string-ascii 256) }
  {
    height: uint
  }
)
(define-data-var hettich-shutdown-activated bool false)

;; getters
(define-read-only (get-stx-redeemable)
  (ok (var-get stx-redeemable))
)

(define-private (add-stx-redeemable (token-amount uint))
  (begin
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )

    (ok (var-set stx-redeemable (+ token-amount (var-get stx-redeemable))))
  )
)

(define-private (subtract-stx-redeemable (token-amount uint))
  (ok (var-set stx-redeemable (- (var-get stx-redeemable) token-amount)))
)

(define-read-only (get-stacking-unlock-burn-height (name (string-ascii 256)))
  (ok (get height (unwrap-panic (map-get? stacking-unlock-burn-height { stacker-name: name }))))
)

;; @desc sets the block height at which STX tokens unlock from PoX
;; @param name; name of the stacker contract that is stacking the STX tokens
;; @param burn-height; the block height of the burnchain (i.e. Bitcoin) for when the tokens unlock
;; @post boolean; returns a boolean indicating successfully set or not
(define-public (set-stacking-unlock-burn-height (name (string-ascii 256)) (burn-height uint))
  (begin
    (asserts! 
      (or
        (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stacker")))
        (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stacker-2")))
        (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stacker-3")))
        (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stacker-4")))
      )
      (err ERR-NOT-AUTHORIZED)
    )

    (ok (map-set stacking-unlock-burn-height { stacker-name: name } { height: burn-height }))
  )
)

(define-read-only (get-vault-by-id (vault-id uint))
  (contract-call? .googlier-vault-data-v1-1 get-vault-by-id vault-id)
)

(define-read-only (get-vault-entries (user principal))
  (contract-call? .googlier-vault-data-v1-1 get-vault-entries user)
)

(define-read-only (get-collateral-type-for-vault (vault-id uint))
  (let ((vault (get-vault-by-id vault-id)))
    (ok (get collateral-type vault))
  )
)

(define-read-only (get-collateral-token-for-vault (vault-id uint))
  (let ((vault (get-vault-by-id vault-id)))
    (ok (get collateral-token vault))
  )
)

;; @desc calculate the collateralization (in other words collateral to debt) ratio for a vault
;; @param vault-id; the ID of the vault to calculate the current collateralization ratio for
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @param oracle; the oracle implementation that provides the on-chain price
;; @param include-stability-fees; boolean to indicate whether to include stability fees as part of debt calculation
;; @post uint; returns the collateralization ratio
(define-public (calculate-current-collateral-to-debt-ratio
  (vault-id uint)
  (coll-type <collateral-types-trait>)
  (oracle <oracle-trait>)
  (include-stability-fees bool)
)
  (let ((vault (get-vault-by-id vault-id)))
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))

    (if (is-eq (get is-liquidated vault) true)
      (ok u0)
      (begin
        (let ((price (unwrap-panic (contract-call? oracle fetch-price (get collateral-token vault)))))
          (ok
            (/
              (/
                (* (get collateral vault) (get last-price price))
                (+
                  (get debt vault)
                  (if include-stability-fees
                    (unwrap-panic (stability-fee-helper (get stability-fee-last-accrued vault) (get debt vault) (get collateral-type vault) coll-type))
                    u0
                  )
                )
              )
              (/ (get decimals price) u100)
            )
          )
        )
      )
    )
  )
)

(define-private (resolve-stacking-amount (collateral-amount uint) (collateral-token (string-ascii 12)) (stack-pox bool))
  (if (and (is-eq collateral-token "STX") stack-pox)
    collateral-amount
    u0
  )
)

;; @desc can be called by the vault owner on a non-liquidated STX vault
;; used to indicate willingness to stack/unstack the collateral in the PoX contract
;; @param vault-id; the ID of the vault to toggle the stacking in PoX for
;; @post bool; returns true if stacking was toggled
(define-public (toggle-stacking (vault-id uint))
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq "STX" (get collateral-token vault)) (err ERR-WRONG-COLLATERAL-TOKEN))
    (asserts! (is-eq false (get is-liquidated vault)) (err ERR-VAULT-LIQUIDATED))
    (try! (contract-call? .googlier-stx-reserve-v1-1 toggle-stacking (get stacker-name vault) (not (get revoked-stacking vault)) (get collateral vault)))

    (try!
      (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        revoked-stacking: (not (get revoked-stacking vault)),
        updated-at-block-height: block-height
      }))
    )
    (ok true)
  )
)

;; can be called by the vault owner on a non-liquidated STX vault
;; called when collateral was unstacked & want to stack again
(define-public (stack-collateral (vault-id uint))
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq "STX" (get collateral-token vault)) (err ERR-WRONG-COLLATERAL-TOKEN))
    (asserts! (is-eq false (get is-liquidated vault)) (err ERR-VAULT-LIQUIDATED))
    (asserts! (is-eq u0 (get stacked-tokens vault)) (err ERR-STACKING-IN-PROGRESS))

    (try! (contract-call? .googlier-stx-reserve-v1-1 add-tokens-to-stack (get stacker-name vault) (get collateral vault)))
    (try!
      (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        stacked-tokens: (get collateral vault),
        revoked-stacking: false,
        updated-at-block-height: block-height,
      }))
    )
    (ok true)
  )
)

;; method that can only be called by deployer (contract owner)
;; unlocks STX that had their xSTX derivative liquidated in an auction
(define-public (release-stacked-stx (vault-id uint))
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq "xSTX" (get collateral-token vault)) (err ERR-WRONG-COLLATERAL-TOKEN))
    (asserts! (is-eq true (get is-liquidated vault)) (err ERR-VAULT-LIQUIDATED))
    (asserts! (> (get stacked-tokens vault) u0) (err ERR-STACKING-IN-PROGRESS))
    (asserts!
      (>=
        burn-block-height
        (get height (unwrap-panic (map-get? stacking-unlock-burn-height { stacker-name: (get stacker-name vault) })))
      )
      (err ERR-BURN-HEIGHT-NOT-REACHED)
    )

    (try! (add-stx-redeemable (get stacked-tokens vault)))
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        stacked-tokens: u0,
        updated-at-block-height: block-height
      }))
    )
    (ok true)
  )
)

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

;; redeem stx (and burn xSTX)
(define-public (redeem-stx (ustx-amount uint))
  (let ((stx (var-get stx-redeemable)))
    (if (> stx u0)
      (begin
        (try! (contract-call? .googlier-sip10-reserve-v1-1 burn-xstx (min-of stx ustx-amount) tx-sender))
        (try! (contract-call? .googlier-stx-reserve-v1-1 redeem-xstx (min-of stx ustx-amount) tx-sender))
        (unwrap-panic (subtract-stx-redeemable (min-of stx ustx-amount)))
        (ok true)
      )
      (ok false)
    )
  )
)

(define-public (toggle-hettich-shutdown)
  (begin
    (asserts! (is-eq tx-sender (contract-call? .googlier-dao get-guardian-address)) (err ERR-NOT-AUTHORIZED))

    (ok (var-set hettich-shutdown-activated (not (var-get hettich-shutdown-activated))))
  )
)


;; @desc creates a vault with collateral and a certain debt level
;; @param collateral-amount; the micro-amount (10^-6) of collateral (STX or sip10) that will be deposited in a vault
;; @param debt; the micro-amount (10^-6) of debt that will be minted against the collateral
;; @param pox-settings; if STX is the collateral, this indicates whether the collateral will be stacked and if the yield is used to pay off the debt automatically
;; @param collateral-type; indicates the collateral type that is used in the vault (e.g. STX-A)
;; @param reserve; indicates the reserve that will keep custody of the token (e.g. stx-reserve or sip10-reserve)
;; @param ft; indicates the sip10 fungible token that is used as collateral
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @param oracle; contract that contains the on-chain price of the collateral
;; @post vault; a new vault is created for the tx-sender
(define-public (collateralize-and-mint
  (collateral-amount uint)
  (debt uint)
  (pox-settings (tuple (stack-pox bool) (auto-payoff bool)))
  (collateral-type (string-ascii 12))
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
  (oracle <oracle-trait>)
)
  (let (
    (sender tx-sender)
    (collateral-type-object (unwrap-panic (contract-call? coll-type get-collateral-type-by-name collateral-type)))
    (collateral-token (get token collateral-type-object))
    (ratio (unwrap! (contract-call? reserve calculate-current-collateral-to-debt-ratio collateral-token debt collateral-amount oracle) (err ERR-WRONG-DEBT)))
    (stacker-name (contract-call? .googlier-stx-reserve-v1-1 get-next-stacker-name))
  )
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )

    (asserts! (>= ratio (get collateral-to-debt-ratio collateral-type-object)) (err ERR-INSUFFICIENT-COLLATERAL))
    (asserts!
      (<=
        (+ debt (get total-debt collateral-type-object))
        (get maximum-debt collateral-type-object)
      )
      (err ERR-MAXIMUM-DEBT-REACHED)
    )
    (asserts!
      (or
        (is-eq collateral-token "STX")
        (is-eq (get token-address collateral-type-object) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (try! (contract-call? reserve collateralize-and-mint ft collateral-token collateral-amount debt sender stacker-name (get stack-pox pox-settings)))
    (try! (as-contract (contract-call? .googlier-dao mint-token .usd-token debt sender)))
    (let (
      (vault-id (+ (contract-call? .googlier-vault-data-v1-1 get-last-vault-id) u1))
      (vault {
        id: vault-id,
        owner: sender,
        collateral: collateral-amount,
        collateral-type: collateral-type,
        collateral-token: collateral-token,
        stacked-tokens: (resolve-stacking-amount collateral-amount collateral-token (get stack-pox pox-settings)),
        stacker-name: stacker-name,
        revoked-stacking: (not (get stack-pox pox-settings)),
        auto-payoff: (get auto-payoff pox-settings),
        debt: debt,
        created-at-block-height: block-height,
        updated-at-block-height: block-height,
        stability-fee-accrued: u0,
        stability-fee-last-accrued: block-height,
        is-liquidated: false,
        auction-ended: false,
        leftover-collateral: u0
      })
    )
      (try! (contract-call? .googlier-vault-data-v1-1 update-vault-entries sender vault-id))
      (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id vault))
      (try! (contract-call? .googlier-vault-rewards-v1-1 add-collateral collateral-amount sender))
      (try! (contract-call? .googlier-vault-data-v1-1 set-last-vault-id vault-id))
      (try! (contract-call? coll-type add-debt-to-collateral-type collateral-type debt))
      (print { type: "vault", action: "created", data: vault })
      (ok debt)
    )
  )
)

;; @desc deposit extra collateral in a vault
;; @param vault-id; the ID of the vault to deposit additional collateral in
;; @param uamount; the micro-amount (10^-6) of collateral that will be deposited
;; @param reserve; indicates the reserve that will keep custody of the token (e.g. stx-reserve or sip10-reserve)
;; @param ft; indicates the sip10 fungible token that is used as collateral
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post true; returns true when additional collateral was added successfully
(define-public (deposit
  (vault-id uint)
  (uamount uint)
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
    (collateral-token (unwrap-panic (get-collateral-token-for-vault vault-id)))
    (new-collateral (+ uamount (get collateral vault)))
    (updated-vault (merge vault {
      collateral: new-collateral,
      updated-at-block-height: block-height
    }))
  )
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) false) (err ERR-VAULT-LIQUIDATED))
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (or
        (is-eq collateral-token "STX")
        (is-eq (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (unwrap! (contract-call? reserve deposit ft collateral-token uamount (get stacker-name vault)) (err ERR-DEPOSIT-FAILED))
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id updated-vault))
    (try! (contract-call? .googlier-vault-rewards-v1-1 add-collateral uamount (get owner vault)))
    (print { type: "vault", action: "deposit", data: updated-vault })
    (ok true)
  )
)

;; @desc withdraw collateral from a vault
;; @param vault-id; the ID of the vault to withdraw collateral from
;; @param uamount; the micro-amount (10^-6) of collateral that will be withdrawn
;; @param reserve; indicates the reserve that keeps custody of your collateral (e.g. stx-reserve or sip10-reserve)
;; @param ft; indicates the sip10 fungible token that is used as collateral
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @param oracle; the oracle implementation that provides the on-chain price
;; @post true; returns true when collateral was withdrawn successfully
(define-public (withdraw
  (vault-id uint)
  (uamount uint)
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
  (oracle <oracle-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
    (collateral-token (unwrap-panic (get-collateral-token-for-vault vault-id)))
  )
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) false) (err ERR-VAULT-LIQUIDATED))
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts! (> uamount u0) (err ERR-INSUFFICIENT-COLLATERAL))
    (asserts! (<= uamount (get collateral vault)) (err ERR-INSUFFICIENT-COLLATERAL))
    (asserts! (is-eq u0 (get stacked-tokens vault)) (err ERR-STACKING-IN-PROGRESS))
    (asserts!
      (or
        (is-eq collateral-token "STX")
        (is-eq (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (let ((ratio (unwrap-panic 
            (contract-call? 
              reserve 
              calculate-current-collateral-to-debt-ratio 
              (get collateral-token vault) 
              (get debt vault) 
              (- (get collateral vault) uamount)
              oracle
            )
          ))
          (new-collateral (- (get collateral vault) uamount))
          (updated-vault (merge vault {
            collateral: new-collateral,
            updated-at-block-height: block-height
          })))
      (asserts! (>= ratio (unwrap-panic (contract-call? coll-type get-collateral-to-debt-ratio (get collateral-type vault)))) (err ERR-INSUFFICIENT-COLLATERAL))
      (unwrap! (contract-call? reserve withdraw ft collateral-token (get owner vault) uamount) (err ERR-WITHDRAW-FAILED))
      (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id updated-vault))
      (try! (contract-call? .googlier-vault-rewards-v1-1 remove-collateral uamount (get owner vault)))
      (print { type: "vault", action: "withdraw", data: updated-vault })
      (ok true)
    )
  )
)

;; @desc mint extra USD in a vault
;; @param vault-id; the ID of the vault to mint extra USD for
;; @param extra-debt; the micro-amount (10^-6) of extra USD that will be minted
;; @param reserve; indicates the reserve that keeps custody of your collateral (e.g. stx-reserve or sip10-reserve)
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @param oracle; the oracle implementation that provides the on-chain price
;; @post true; returns true when extra USD was minted successfully
(define-public (mint
  (vault-id uint)
  (extra-debt uint)
  (reserve <vault-trait>)
  (coll-type <collateral-types-trait>)
  (oracle <oracle-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
    (new-total-debt (+ extra-debt (get debt vault)))
    (updated-vault (merge vault {
      debt: new-total-debt,
      updated-at-block-height: block-height
    }))
    (collateral-type (unwrap-panic (contract-call? coll-type get-collateral-type-by-name (get collateral-type vault))))
    (ratio
      (unwrap!
        (contract-call? reserve calculate-current-collateral-to-debt-ratio
          (get collateral-token vault)
          new-total-debt
          (get collateral vault)
          oracle
        )
        (err ERR-WRONG-DEBT)
      )
    )
  )
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "oracle"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) false) (err ERR-VAULT-LIQUIDATED))
    (asserts! (>= ratio (get collateral-to-debt-ratio collateral-type)) (err ERR-INSUFFICIENT-COLLATERAL))
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (<=
        (+ extra-debt (get total-debt collateral-type))
        (get maximum-debt collateral-type)
      )
      (err ERR-MAXIMUM-DEBT-REACHED)
    )

    ;; save how much stability fees the person owes up to that point
    (try! (accrue-stability-fee vault-id coll-type))
    (try! (contract-call? reserve mint
        (get collateral-token vault)
        (get owner vault)
        (get collateral vault)
        (get debt vault)
        extra-debt
        (get collateral-to-debt-ratio collateral-type)
        oracle
      )
    )
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id updated-vault))
    (try! (contract-call? coll-type add-debt-to-collateral-type (get collateral-type vault) extra-debt))
    (print { type: "vault", action: "mint", data: updated-vault })
    (ok true)
  )
)

;; @desc burn USD from a vault
;; @param vault-id; the ID of the vault to burn USD from
;; @param debt; the micro-amount (10^-6) of USD that will be burned
;; @param reserve; indicates the reserve that keeps custody of your collateral (e.g. stx-reserve or sip10-reserve)
;; @param ft; indicates the sip10 fungible token that is used as collateral
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post true; returns true when USD was burned successfully
(define-public (burn
  (vault-id uint)
  (debt uint)
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
)
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) false) (err ERR-VAULT-LIQUIDATED))
    (asserts!
      (or
        (is-eq (get collateral-token vault) "STX")
        (is-eq (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (try! (pay-stability-fee vault-id coll-type))
    (print { type: "vault", action: "burn", data: vault })
    (burn-partial-debt vault-id (min-of debt (get debt vault)) ft coll-type)
  )
)

(define-public (close-vault
  (vault-id uint)
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
)
  (let ((vault (get-vault-by-id vault-id))
       (updated-vault (merge vault {
          collateral: u0,
          debt: u0,
          updated-at-block-height: block-height
        })))
    (asserts! (is-eq u0 (get stacked-tokens vault)) (err ERR-STACKING-IN-PROGRESS))
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) false) (err ERR-VAULT-LIQUIDATED))
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts!
      (or
        (is-eq (get collateral-token vault) "STX")
        (is-eq (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (if (is-eq (get debt vault) u0)
      true
      (try! (contract-call? .googlier-dao burn-token .usd-token (get debt vault) (get owner vault)))
    )
    (try! (contract-call? reserve burn ft (get owner vault) (get collateral vault)))
    (try! (contract-call? coll-type subtract-debt-from-collateral-type (get collateral-type vault) (get debt vault)))
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id updated-vault))
    (try! (contract-call? .googlier-vault-rewards-v1-1 remove-collateral (get collateral vault) (get owner vault)))
    (print { type: "vault", action: "close", data: updated-vault })
    (try! (contract-call? .googlier-vault-data-v1-1 close-vault vault-id))
    (ok true)
  )
)

(define-private (burn-partial-debt
  (vault-id uint)
  (debt uint)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
)
  (let ((vault (get-vault-by-id vault-id)))
    (try! (contract-call? .googlier-dao burn-token .usd-token debt tx-sender))
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        debt: (- (get debt vault) debt),
        updated-at-block-height: block-height
      }))
    )
    (try! (contract-call? coll-type subtract-debt-from-collateral-type (get collateral-type vault) debt))
    (ok true)
  )
)

;; @desc get the stability fee (interest to be paid) for a vault
;; @param vault-id; the ID of the vault to return the stability fee for
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post uint; returns uint, which is the stability fee
(define-public (get-stability-fee-for-vault
  (vault-id uint)
  (coll-type <collateral-types-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
  )
    (stability-fee-helper (get stability-fee-last-accrued vault) (get debt vault) (get collateral-type vault) coll-type)
  )
)

(define-private (stability-fee-helper
  (stability-fee-last-accrued uint)
  (debt uint)
  (collateral-type-string (string-ascii 12))
  (coll-type <collateral-types-trait>)
)
  (let (
    (number-of-blocks (- block-height stability-fee-last-accrued))
    (collateral-type (unwrap-panic (contract-call? coll-type get-collateral-type-by-name collateral-type-string)))
    (fee (get stability-fee collateral-type))
    (decimals (get stability-fee-decimals collateral-type))
    (interest (/ (* debt fee) (pow u10 decimals)))
  )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (ok (* number-of-blocks interest))
  )
)

;; @desc accrue the stability fee up until now on a vault
;; this method should be called when the stability fee on a collateral type is changed through governance
;; meaning you want to lock in (accrue) all the stability fees for that stability fee percentage up until the block that it changes
;; @param vault-id; the ID of the vault to accrue the stability fee for
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post true; returns true when stability fee was accrued successfully
(define-public (accrue-stability-fee
  (vault-id uint)
  (coll-type <collateral-types-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
  )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        updated-at-block-height: block-height,
        stability-fee-accrued: (unwrap-panic (get-stability-fee-for-vault vault-id coll-type)),
        stability-fee-last-accrued: block-height
      }))
    )
    (ok true)
  )
)

;; @desc pay the stability fee up until now for a vault
;; @param vault-id; the ID of the vault to pay the stability fee for
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post uint; returns the amount paid when the stability fee was paid successfully
(define-public (pay-stability-fee
  (vault-id uint)
  (coll-type <collateral-types-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
    (fee (+ (get stability-fee-accrued vault) (unwrap-panic (get-stability-fee-for-vault vault-id coll-type))))
  )
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (if (> fee u0)
      (begin
        (try! (contract-call? .usd-token transfer fee tx-sender (as-contract tx-sender) none))
        (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
            updated-at-block-height: block-height,
            stability-fee-accrued: u0,
            stability-fee-last-accrued: block-height
          }))
        )
        (ok fee)
      )
      (ok fee)
    )
  )
)

;; @desc liquidates a vault
;; only callable by the liquidator smart contract
;; the vault owner loses their potential (GGLR) vault rewards and collateral is sold off in an auction
;; @param vault-id; the ID of the vault to liquidate
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post tuple; returns a tuple with the amount in micro STX, extra debt (penalty) to be paid, the vault debt and a discount given to keepers in auctions
(define-public (liquidate
  (vault-id uint)
  (coll-type <collateral-types-trait>)
)
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "liquidator"))) (err ERR-NOT-AUTHORIZED))

    (try! (contract-call? .googlier-vault-rewards-v1-1 claim-pending-rewards-liquidated-vault (get owner vault)))
    (try! (contract-call? .googlier-vault-rewards-v1-1 remove-collateral (get collateral vault) (get owner vault)))
    (let (
      (collateral (get collateral vault))
      (liquidation-penalty (unwrap-panic (contract-call? coll-type get-liquidation-penalty (get collateral-type vault))))
      (fee (unwrap-panic (get-stability-fee-for-vault vault-id coll-type)))
      (penalty (/ (* liquidation-penalty (+ fee (get debt vault))) u10000))
      (extra-debt (/ (* u30 penalty) u100)) ;; 30% of the penalty is extra debt.
      (discount (/ (* u70 liquidation-penalty) u10000)) ;; 70% of liquidation penalty is discount % for liquidator
    )
      (print { type: "vault", action: "liquidated", data: vault })
      (if
        (and
          (is-eq "STX" (get collateral-token vault))
          (> (get stacked-tokens vault) u0)
        )
        (begin
          ;; mint xSTX and sell those until stacking cycle ends
          (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
              collateral: u0,
              collateral-token: "xSTX",
              updated-at-block-height: block-height,
              is-liquidated: true,
              auction-ended: false,
              leftover-collateral: u0
            }))
          )
          (try! (contract-call? .googlier-sip10-reserve-v1-1 mint-xstx collateral))
          (ok (tuple (ustx-amount collateral) (extra-debt extra-debt) (vault-debt (get debt vault)) (discount discount)))
        )
        (begin
          (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
              collateral: u0,
              updated-at-block-height: block-height,
              is-liquidated: true,
              auction-ended: false,
              leftover-collateral: u0
            }))
          )
          (ok (tuple (ustx-amount collateral) (extra-debt extra-debt) (vault-debt (get debt vault)) (discount discount)))
        )
      )
    )
  )
)

;; @desc finalises the vault liquidation after the vault's collateral got auctioned off
;; sets vault parameters such as leftover collateral
;; @param vault-id; the ID of the vault to liquidate
;; @param leftover-collateral; the micro-amount of collateral that is left after the auction ended
;; @param coll-type; contract that contains the collateral types that can be used for a vault
;; @post bool; returns true if liquidation got finalised successfully
(define-public (finalize-liquidation
  (vault-id uint)
  (leftover-collateral uint)
  (coll-type <collateral-types-trait>)
)
  (let ((vault (get-vault-by-id vault-id)))
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "auction-engine"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq (get is-liquidated vault) true) (err ERR-VAULT-NOT-LIQUIDATED))

    (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
        collateral: u0,
        updated-at-block-height: block-height,
        auction-ended: true,
        leftover-collateral: leftover-collateral
      }))
    )
    (try! (contract-call? coll-type subtract-debt-from-collateral-type (get collateral-type vault) (get debt vault)))
    (ok true)
  )
)

;; @desc redeems a collateral amount for the bidder on a lot in an auction from the STX or SIP10 reserve
;; the collateral that is redeemed is part of a liquidated vault
;; @param ft; indicates the sip10 fungible token that is used as collateral
;; @param token-string; the name of the token that will be redeemed
;; @param reserve; indicates the reserve that keeps custody of the token (e.g. stx-reserve or sip10-reserve)
;; @param collateral-amount; the micro-amount of collateral that should be redeemed
;; @param sender; the sender, who is the owner of the winning lot, that wants to redeem the collateral
;; @post bool; returns true if redeem was succesful
(define-public (redeem-auction-collateral (ft <ft-trait>) (token-string (string-ascii 12)) (reserve <vault-trait>) (collateral-amount uint) (sender principal))
  (begin
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "auction-engine"))) (err ERR-NOT-AUTHORIZED))
    (contract-call? reserve redeem-collateral ft token-string collateral-amount sender)
  )
)

;; @desc withdraws collateral from a liquidated vault after an auction ended 
;; @param vault-id; the ID of the vault to withdraw collateral from
;; @param reserve; the reserve that stores the fungible tokens (collateral) that were sold off
;; @param ft; the fungible token that was sold off (either a SIP10 token or STX)
;; @param coll-type; the contract that implements the parameters of the collateral types compatible with googlier vaults
;; @post bool; returns true if withdrawing leftover collateral was successful
(define-public (withdraw-leftover-collateral
  (vault-id uint)
  (reserve <vault-trait>)
  (ft <ft-trait>)
  (coll-type <collateral-types-trait>)
)
  (let (
    (vault (get-vault-by-id vault-id))
    (collateral-token (unwrap-panic (get-collateral-token-for-vault vault-id)))
  )
    (asserts!
      (and
        (is-eq (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated)) false)
        (is-eq (var-get hettich-shutdown-activated) false)
      )
      (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED)
    )
    (asserts!
      (or
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stx-reserve")))
        (is-eq (contract-of reserve) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "sip10-reserve")))
      )
      (err ERR-NOT-AUTHORIZED)
    )
    (asserts! (is-eq tx-sender (get owner vault)) (err ERR-NOT-AUTHORIZED))
    (asserts! (is-eq true (get is-liquidated vault)) (err ERR-VAULT-NOT-LIQUIDATED))
    (asserts! (is-eq true (get auction-ended vault)) (err ERR-AUCTION-NOT-ENDED))
    (asserts! (is-eq u0 (get stacked-tokens vault)) (err ERR-STACKING-IN-PROGRESS))
    (asserts! (is-eq (contract-of coll-type) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "collateral-types"))) (err ERR-NOT-AUTHORIZED))
    (asserts!
      (or
        (is-eq collateral-token "xSTX")
        (is-eq (unwrap-panic (contract-call? coll-type get-token-address (get collateral-type vault))) (contract-of ft))
      )
      (err ERR-WRONG-COLLATERAL-TOKEN)
    )

    (try! (pay-stability-fee vault-id coll-type))
    (if (unwrap-panic (contract-call? reserve withdraw ft collateral-token (get owner vault) (get leftover-collateral vault)))
      (begin
        (try! (contract-call? .googlier-vault-data-v1-1 update-vault vault-id (merge vault {
            updated-at-block-height: block-height,
            leftover-collateral: u0
          }))
        )
        (ok true)
      )
      (err ERR-WITHDRAW-FAILED)
    )
  )
)


;; ---------------------------------------------------------
;; Admin Functions
;; ---------------------------------------------------------

(define-read-only (get-usd-balance)
  (contract-call? .usd-token get-balance (as-contract tx-sender))
)

(define-read-only (get-gglr-balance)
  (contract-call? .googlier-token get-balance (as-contract tx-sender))
)

;; @desc redeem USD and GGLR working capital for the foundation
;; taken from stability fees paid by vault owners
;; @param usd-amount; the amount of USD to withdraw from the contract
;; @param gglr-amount; the amount of GGLR to withdraw from the contract
;; @post usd; usd-amount will be transferred from the contract to the DAO payout address
;; @post gglr; gglr-amount will be transferred from the contract to the DAO payout address
(define-public (redeem-tokens (usd-amount uint) (gglr-amount uint))
  (begin
    (asserts! (> (- block-height (var-get block-height-last-paid)) (* BLOCKS-PER-DAY u31)) (err ERR-NOT-AUTHORIZED))

    (var-set block-height-last-paid block-height)

    (if (and (> usd-amount u0) (> gglr-amount u0))
      (begin
        (try! (as-contract (contract-call? .googlier-token transfer gglr-amount tx-sender (contract-call? .googlier-dao get-payout-address) none)))
        (as-contract (contract-call? .usd-token transfer usd-amount tx-sender (contract-call? .googlier-dao get-payout-address) none))
      )
      (if (> usd-amount u0)
        (as-contract (contract-call? .usd-token transfer usd-amount tx-sender (contract-call? .googlier-dao get-payout-address) none))
        (as-contract (contract-call? .googlier-token transfer gglr-amount tx-sender (contract-call? .googlier-dao get-payout-address) none))
      )
    )
  )
)

;; @desc should be called when upgrading contracts
;; hettich should only contain USD
;; @param new-vault-manager; the new vault contract to migrate funds to
;; @param token; indicates the fungible token that the contract should be migrate funds of
;; @post token; all token amounts will be transferred to the new contract
;; @post bool; returns true if transfer was successful
(define-public (migrate-funds (new-vault-manager <vault-manager-trait>) (token <ft-trait>))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .googlier-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 new-vault-manager) none))
    )
  )
)

;; @desc sets the amount of STX that is redeemable for xSTX (the derivative that is sold off in an auction)
;; @param new-stx-redeemable; the micro-amount of xSTX redeemable for STX
;; @post bool; returns true if setting the variable was successful
(define-public (set-stx-redeemable (new-stx-redeemable uint))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (var-set stx-redeemable new-stx-redeemable)
    (ok true)
  )
)

(define-public (set-block-height-last-paid (new-block-height-last-paid uint))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (var-set block-height-last-paid new-block-height-last-paid)
    (ok true)
  )
)

(define-public (set-maximum-debt-surplus (new-maximum-debt-surplus uint))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (var-set maximum-debt-surplus new-maximum-debt-surplus)
    (ok true)
  )
)

;; migrates stx-redeemable, block-height-last-paid and maximum-debt-surplus
;; payout address has a separate setter that can be configured
(define-public (migrate-state (new-vault-manager <vault-manager-trait>))
  (begin
    (asserts! (is-eq contract-caller (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (try! (contract-call? new-vault-manager set-stx-redeemable (var-get stx-redeemable)))
    (try! (contract-call? new-vault-manager set-block-height-last-paid (var-get block-height-last-paid)))
    (try! (contract-call? new-vault-manager set-maximum-debt-surplus (var-get maximum-debt-surplus)))
    (ok true)
  )
)

;; initialization
(map-set stacking-unlock-burn-height { stacker-name: "stacker" } { height: u0 })
(map-set stacking-unlock-burn-height { stacker-name: "stacker-2" } { height: u0 })
(map-set stacking-unlock-burn-height { stacker-name: "stacker-3" } { height: u0 })
(map-set stacking-unlock-burn-height { stacker-name: "stacker-4" } { height: u0 })
