Source Code

;; @contract Core
;; @version 1

;;-------------------------------------
;; Traits 
;;-------------------------------------

(use-trait pnl-calculator-trait .pnl-calculator-trait.pnl-calculator-trait)
(use-trait pyth-storage-trait 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-traits-v1.storage-trait)
(use-trait pyth-decoder-trait 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-traits-v1.decoder-trait)
(use-trait wormhole-core-trait 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.wormhole-traits-v1.core-trait)

;;-------------------------------------
;; Errors 
;;-------------------------------------

(define-constant ERR_NO_ENTRY_FOR_ID (err u2001))
(define-constant ERR_NO_ENTRY_FOR_COUNTERPARTY (err u2002))
(define-constant ERR_SETTLEMENT_RATE_NONE (err u2003))
(define-constant ERR_PNL_USD_SET_NONE (err u2004))
(define-constant ERR_NOT_OPTION_ASSET (err u2005))
(define-constant ERR_EPOCH_NOT_EXPIRED (err u2006))
(define-constant ERR_SETTLEMENT_WINDOW_CLOSED (err u2007))
(define-constant ERR_PAYMENT_OUTSTANDING (err u2008))
(define-constant ERR_EPOCH_ALREADY_SETTLED (err u2009))
(define-constant ERR_TRADING_NOT_ALLOWED (err u2010))
(define-constant ERR_EXPIRY_BEFORE_CURRENT_EXPIRY (err u2011))
(define-constant ERR_EPOCH_ALREADY_STARTED_FUNDS_REGISTERED (err u2012))
(define-constant ERR_EPOCH_ALREADY_STARTED_UNITS_TRANSACTED (err u2013))
(define-constant ERR_CURRENT_EPOCH_NOT_SETTLED (err u2014))
(define-constant ERR_VIOLATES_MIN_EPOCH_DURATION (err u2015))
(define-constant ERR_VIOLATES_MAX_EPOCH_DURATION (err u2016))
(define-constant ERR_EPOCH_ZERO_TRADING_ATTEMPT (err u2017))
(define-constant ERR_NOT_STANDARD (err u2018))
(define-constant ERR_NOT_ENOUGH_FUNDS_AVAILABLE (err u2019))
(define-constant ERR_CURRENT_EPOCH_SETTLED (err u2020))
(define-constant ERR_REGISTRATION_WINDOW_CLOSED (err u2021))
(define-constant ERR_NO_REGISTRATION_FOR_COUNTERPARTY (err u2022))
(define-constant ERR_AMOUNT_MISMATCH (err u2023))
(define-constant ERR_PRICE_MISMATCH (err u2024))
(define-constant ERR_CONFIRMATION_WINDOW_CLOSED (err u2025))
(define-constant ERR_PNL_UNDETERMINED (err u2026))
(define-constant ERR_PAYMENT_WINDOW_CLOSED (err u2027))
(define-constant ERR_PAYMENT_AMOUNTS_DONT_MATCH (err u2028))
(define-constant ERR_ALREADY_INITIALIZED (err u2029))
(define-constant ERR_PAYMENT_WINDOW_STILL_OPEN (err u2030))
(define-constant ERR_PNL_ALREADY_DETERMINED (err u2031))
(define-constant ERR_PNL_CALCULATION_WINDOW_STILL_OPEN (err u2032))

;;-------------------------------------
;; Constants 
;;-------------------------------------

(define-constant option-asset-symbol 0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17) ;; "STX" price feed id
(define-constant underlying-base (pow u10 u6))
(define-constant bps-base u10000) ;; 1 bps = 0.01%
(define-constant minute-in-ms u60000)

;;-------------------------------------
;; Variables 
;;-------------------------------------

(define-data-var is-initialized bool false)

(define-data-var current-epoch-id uint u0)

(define-data-var trading-funds-available uint u0)
(define-data-var trading-funds-registered uint u0)
(define-data-var total-funds-requested uint u0)

(define-data-var counterparty-addresses (list 5000 principal) (list))
(define-data-var delinquent-counterparty-addresses (list 5000 principal) (list))
(define-data-var delinquent-epoch bool false)

(define-data-var helper-principal principal tx-sender)

;;-------------------------------------
;; Maps 
;;-------------------------------------

(define-map epoch-info
  { 
    epoch-id: uint
  }
  { 
    epoch-expiry: uint, ;; unix timestamp in ms
    strike-call: (optional uint), ;; usd-per-option-asset
    strike-put: (optional uint), ;; usd-per-option-asset
    barrier-up: (optional uint), ;; usd-per-option-asset
    barrier-down: (optional uint), ;; usd-per-option-asset
    unit-pnl: (optional uint), ;; in underlying
    settlement-rate: (optional uint), ;; usd-per-option-asset
    block-height-start: uint,
    block-height-pnl: (optional uint),
    block-height-settled: (optional uint),
    total-units-transacted: uint,
    total-premium: uint, ;; in underlying
    total-underlying-active: uint,
    underlying-per-token-settled: (optional uint),
    epoch-risk: uint, ;; in bps
    unit-size: uint
  }
)

(define-map counterparty-info
  {
    address: principal
  }
  { 
    units-registered: uint,
    price-registered: (optional uint),
    block-height-registered: (optional uint),
    units-transacted: uint,
    funds-requested: (optional uint),
  }
)

(define-map delinquent-counterparty-info 
  {
    address: principal
  }
  { 		
    funds-requested: uint,
    units-transacted: uint,
    epoch-id: uint,
  }
)

;;-------------------------------------
;; Getters 
;;-------------------------------------

(define-read-only (get-current-epoch-id) 
  (var-get current-epoch-id))

(define-read-only (get-trading-funds-available) 
  (var-get trading-funds-available))

(define-read-only (get-trading-funds-registered) 
  (var-get trading-funds-registered))

(define-read-only (get-total-funds-requested) 
  (var-get total-funds-requested))

(define-read-only (get-epoch-info (epoch-id uint)) 
  (ok (unwrap! (map-get? epoch-info { epoch-id: epoch-id }) ERR_NO_ENTRY_FOR_ID)))

(define-read-only (get-current-epoch-info) 
  (map-get? epoch-info { epoch-id: (get-current-epoch-id) }))

(define-read-only (get-counterparty-info (counterparty principal)) 
  (ok (unwrap! (map-get? counterparty-info { address: counterparty }) ERR_NO_ENTRY_FOR_COUNTERPARTY)))

(define-read-only (get-counterparty-addresses) 
  (var-get counterparty-addresses))

(define-read-only (get-delinquent-counterparty-info (counterparty principal)) 
  (ok (unwrap! (map-get? delinquent-counterparty-info { address: counterparty }) ERR_NO_ENTRY_FOR_COUNTERPARTY)))

(define-read-only (get-delinquent-counterparty-addresses) 
  (var-get delinquent-counterparty-addresses))

;;-------------------------------------
;; Settlement
;;-------------------------------------

;; @desc Triggers methods that settle the current epoch
(define-private (settle) 
  (begin 
    (try! (charge-fees))
    (try! (settle-epoch-info))
    (try! (activate-pending-claims))
    (try! (as-contract (contract-call? .ht-hq-ststx-bull-v2 update-fees-and-settings)))
    (unwrap-panic (reset-data))
    (ok true)))

;; @desc Charges performance and management fees
(define-private (charge-fees)
  (let (
    (current-performance-fee (get current (try! (contract-call? .ht-hq-ststx-bull-v2 get-fees "performance"))))
    (current-management-fee (get current (try! (contract-call? .ht-hq-ststx-bull-v2 get-fees "management"))))
    (fee-address (contract-call? .ht-hq-ststx-bull-v2 get-fee-address))
    (no-profit-management-fee (contract-call? .ht-hq-ststx-bull-v2 get-no-profit-management-fee))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (unit-pnl (unwrap-panic (get unit-pnl current-epoch-info)))
    (total-premium (get total-premium current-epoch-info))
    (units-transacted (get total-units-transacted current-epoch-info))
    (total-underlying (get total-underlying-active current-epoch-info))
    (strike-call (get strike-call current-epoch-info))
    (strike-put (get strike-put current-epoch-info))
    (strike-set (or (is-some strike-call) (is-some strike-put))))
    (if (and strike-set (not (var-get delinquent-epoch)))
      (if (> (* unit-pnl units-transacted) total-premium)
        (begin
          (try! 
            (as-contract (contract-call? .ht-vault-ststx-bull-v2 payout-funds
            (/ (* (- (* unit-pnl units-transacted) total-premium) current-performance-fee) bps-base) fee-address))
          )
          (try!
            (as-contract (contract-call? .ht-vault-ststx-bull-v2 payout-funds 
            (/ (/ (* total-underlying current-management-fee) bps-base) u100) fee-address))
          )
        )
        (if no-profit-management-fee
          (try!
            (as-contract (contract-call? .ht-vault-ststx-bull-v2 payout-funds 
            (/ (/ (* total-underlying current-management-fee) bps-base) u100) fee-address))
          )
          true
        )
      )
      true
    )
    (ok true)))

;; @desc Updates the epoch-info map in core and vault with the settled values
(define-private (settle-epoch-info)
  (let (
    (underlying-per-token (contract-call? .ht-vault-ststx-bull-v2 get-underlying-per-token))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))) 
    (map-set epoch-info { epoch-id: (get-current-epoch-id) } 
      (merge
        current-epoch-info
        { 
          block-height-settled: (some burn-block-height),
          underlying-per-token-settled: (some underlying-per-token)
        }
      )
    )
    (as-contract (contract-call? .ht-vault-ststx-bull-v2 update-epoch-info-for-claims))))

;; @desc Updates vault variables that processs deposits and withdrawals
(define-private (activate-pending-claims) 
  (begin
    (try! (as-contract (contract-call? .ht-vault-ststx-bull-v2 activate-pending-deposit-claims)))
    (try! (as-contract (contract-call? .ht-vault-ststx-bull-v2 activate-pending-withdrawal-claims)))
    (ok true)))

;; @desc Initiates trading-funds-available for the next epoch
(define-private (init-trading-funds-available)
  (begin
    (var-set trading-funds-available 
    (/ 
      (* 
        (contract-call? .ht-vault-ststx-bull-v2 get-total-underlying-active)
        (contract-call? .ht-hq-ststx-bull-v2 get-epoch-risk)) 
      bps-base))
    (ok true)))

;; @desc Resets all variables and maps
(define-private (reset-data) 
  (begin
    (map delete-counterparty (var-get counterparty-addresses))
    (var-set counterparty-addresses (list))
    (var-set total-funds-requested u0)
    (var-set trading-funds-registered u0)
    (ok (var-set delinquent-epoch false))))

;; @desc Creates a payment-request for every entry in counterparty-info
;; @param counterparty: principal that has sold option(s) to the contract
(define-private (payment-requester 
  (counterparty-address principal))
  (let (
    (counterparty-info-entry (unwrap-panic (get-counterparty-info counterparty-address)))
    (current-epoch-info (unwrap-panic (get-current-epoch-info))) 
    (units-transacted (get units-transacted counterparty-info-entry))
    (unit-pnl (unwrap-panic (get unit-pnl current-epoch-info)))
    (funds-requested (* units-transacted unit-pnl)))
    (map-set counterparty-info { address: counterparty-address }
      (merge counterparty-info-entry              
        {
          funds-requested: (some funds-requested)
        }
      )
    )
    (ok (var-set total-funds-requested (+ (var-get total-funds-requested) funds-requested)))))

;; @desc Provides pricing data to determine the option pnl and either settles the epoch or requests payment from counterparties
;; @param pnl-calculator-contract: contract address of active pnl-calculator
;; @param use-oracle-price: if false, use the settlement-rate argument as the settlment price
;; @param settlement-rate: usd-per-underlying rate at which the option contract settled (oracle format: 10**8)
;; @param option-asset-per-underlying: option asset to underlying rate i.e. BTCsBTC or STXstSTX (oracle format: 10**8)
;; @price-feed-bytes: pyth price feed data
;; @execution-plan: pyth contracts to be called by oracle
(define-public (determine-pnl
  (pnl-calculator-contract <pnl-calculator-trait>)
  (use-pnl-calculator bool)
  (pnl-usd-set (optional uint))
  (use-oracle-price bool)
  (settlement-rate (optional uint))
  (option-asset-per-underlying (optional uint))
  (price-feed-bytes (buff 8192))
  (execution-plan {
    pyth-storage-contract: <pyth-storage-trait>,
    pyth-decoder-contract: <pyth-decoder-trait>,
    wormhole-core-contract: <wormhole-core-trait>
  }))
  (let (
    (decoded-prices (try! (contract-call? 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-oracle-v2 decode-price-feeds price-feed-bytes execution-plan)))
    (decoded-price (element-at decoded-prices u0))
    (timestamp (* (unwrap-panic (get publish-time decoded-price)) u1000))
    (usd-per-option-asset (if use-oracle-price (to-uint (unwrap-panic (get ema-price decoded-price))) (unwrap! settlement-rate ERR_SETTLEMENT_RATE_NONE)))
    (price-id (unwrap-panic (get price-identifier decoded-price)))
    (epoch-id (get-current-epoch-id))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (current-epoch-expiry (get epoch-expiry current-epoch-info))
    (strike-call (get strike-call current-epoch-info))
    (strike-put (get strike-put current-epoch-info))
    (barrier-up (get barrier-up current-epoch-info))
    (barrier-down (get barrier-down current-epoch-info))
    (option-type (contract-call? .ht-hq-ststx-bull-v2 get-option-type))
    (strategy-type (contract-call? .ht-hq-ststx-bull-v2 get-strategy-type))
    (unit-size (contract-call? .ht-hq-ststx-bull-v2 get-unit-size))
    (pnl-usd (if use-pnl-calculator 
      (try! (contract-call? pnl-calculator-contract calculate-pnl usd-per-option-asset option-type strategy-type strike-call strike-put barrier-up barrier-down))
      (unwrap! pnl-usd-set ERR_PNL_USD_SET_NONE)
    ))
    (pnl-option-asset (usd-to-option-asset pnl-usd usd-per-option-asset))
    (unit-pnl (/ (* (/ (* pnl-option-asset unit-size) underlying-base) underlying-base) 
      (match option-asset-per-underlying value value underlying-base)))
    (total-units-transacted (get total-units-transacted current-epoch-info))
    (total-premium (get total-premium current-epoch-info)))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-updater tx-sender))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-pnl-calculator-active (contract-of pnl-calculator-contract)))
    (asserts! (is-eq price-id option-asset-symbol) ERR_NOT_OPTION_ASSET)
    (asserts! (>= timestamp current-epoch-expiry) ERR_EPOCH_NOT_EXPIRED)
    (asserts! (<= timestamp (+ current-epoch-expiry (contract-call? .ht-hq-ststx-bull-v2 get-pnl-data-window))) ERR_SETTLEMENT_WINDOW_CLOSED)
    (asserts! (is-eq (var-get total-funds-requested) u0) ERR_PAYMENT_OUTSTANDING)
    (asserts! (is-none (get block-height-settled current-epoch-info)) ERR_EPOCH_ALREADY_SETTLED)
    (map-set epoch-info { epoch-id: epoch-id } 
      (merge
        current-epoch-info
        { 
          unit-pnl: (some unit-pnl),
          settlement-rate: (some usd-per-option-asset),
          block-height-pnl: (some burn-block-height)
        }
      )
    )
    (print {
      epoch-id: epoch-id, 
      expiry: current-epoch-expiry,
      settlement-rate: usd-per-option-asset,
      option-pnl: pnl-option-asset,
      unit-pnl: unit-pnl,
      return-pre-fees: (- (to-int (* unit-pnl total-units-transacted)) (to-int total-premium))
    })
    (if (or (is-eq pnl-usd u0) (is-eq total-units-transacted u0))
      (try! (settle))
      (begin
        (map payment-requester (var-get counterparty-addresses))
        true
      )
    )
    (ok true)))

;;-------------------------------------
;; Trading 
;;-------------------------------------

;; @desc Allows admin to start a new epoch and set the expiry, strike and barrier prices
;; @param pnl-calculator-contract: contract address of active pnl-calculator
;; @param expiry: unix timestamp in ms 
;; @param strike-call: strike price in USD for call option (oracle format: 10**8) 
;; @param strike-put: strike price in USD for put option (oracle format: 10**8) 
;; @param barrier-up: upwards barrier in USD (oracle format: 10**8)
;; @param barrier-down: donwards barrier in USD (oracle format: 10**8)
(define-public (start-new-epoch
  (pnl-calculator-contract <pnl-calculator-trait>)
  (expiry uint)
  (strike-call (optional uint))
  (strike-put (optional uint))
  (barrier-up (optional uint))
  (barrier-down (optional uint)))
  (let (
    (new-epoch-id (+ (get-current-epoch-id) u1))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (current-epoch-expiry (get epoch-expiry current-epoch-info))
    (option-type (contract-call? .ht-hq-ststx-bull-v2 get-option-type))
    (strategy-type (contract-call? .ht-hq-ststx-bull-v2 get-strategy-type))
    (current-epoch-settled (is-some (get block-height-settled current-epoch-info))))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-trader tx-sender))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-pnl-calculator-active (contract-of pnl-calculator-contract)))
    (asserts! (contract-call? .ht-hq-ststx-bull-v2 get-trading-allowed) ERR_TRADING_NOT_ALLOWED)
    (try! (contract-call? pnl-calculator-contract check-strike-order option-type strategy-type strike-call strike-put barrier-up barrier-down))
    (asserts! (>= expiry current-epoch-expiry) ERR_EXPIRY_BEFORE_CURRENT_EXPIRY)
    (asserts! (is-eq u0 (var-get trading-funds-registered)) ERR_EPOCH_ALREADY_STARTED_FUNDS_REGISTERED)
    (try! (activate-pending-claims))
    (unwrap-panic (init-trading-funds-available))
    (if (and (is-eq current-epoch-expiry expiry) (not current-epoch-settled))
      (begin
        (asserts! (is-eq u0 (get total-units-transacted current-epoch-info)) ERR_EPOCH_ALREADY_STARTED_UNITS_TRANSACTED)
        (map-set epoch-info { epoch-id: (get-current-epoch-id) } 
          (merge
            current-epoch-info
            { 
              epoch-expiry: expiry,
              strike-call: strike-call,
              strike-put: strike-put,
              barrier-up: barrier-up,
              barrier-down: barrier-down,
            }
          )
        )
      )
      (begin 
        (asserts! current-epoch-settled ERR_CURRENT_EPOCH_NOT_SETTLED) 
        (asserts! (>= expiry (+ current-epoch-expiry (contract-call? .ht-hq-ststx-bull-v2 get-min-epoch-duration))) ERR_VIOLATES_MIN_EPOCH_DURATION)
        (asserts! (<= expiry (+ current-epoch-expiry (contract-call? .ht-hq-ststx-bull-v2 get-max-epoch-duration))) ERR_VIOLATES_MAX_EPOCH_DURATION)
        (map-set epoch-info { epoch-id: new-epoch-id } 
          {
            epoch-expiry: expiry,
            strike-call: strike-call,
            strike-put: strike-put,
            barrier-up: barrier-up,
            barrier-down: barrier-down,
            unit-pnl: none,
            settlement-rate: none,
            block-height-start: burn-block-height,
            block-height-pnl: none,
            block-height-settled: none,
            total-units-transacted: u0,
            total-premium: u0,
            total-underlying-active: (contract-call? .ht-vault-ststx-bull-v2 get-total-underlying-active), 
            underlying-per-token-settled: none,
            epoch-risk: (contract-call? .ht-hq-ststx-bull-v2 get-epoch-risk),
            unit-size: (contract-call? .ht-hq-ststx-bull-v2 get-next-unit-size)
          }
        )
        (try! (as-contract (contract-call? .ht-vault-ststx-bull-v2 create-epoch-info-for-claims)))
        (var-set current-epoch-id new-epoch-id)
      )
    )
    (ok true)))

;; @desc Registers a trade by declaring the counterparty, amount and price
;; @param amount: amount of units to be transacted 
;; @param option-price-per-underlying: price of an option represeting one base unit of underlying (i.e. 1 sBTC / 100,000,000 sats or 1 stSTX)
;; @param counterparty: principal of the counterparty
(define-public (register-trade
  (amount uint)
  (option-price-per-underlying uint)
  (counterparty-address principal))
  (let (
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (funds-outgoing (* amount (/ (* option-price-per-underlying (contract-call? .ht-hq-ststx-bull-v2 get-unit-size)) underlying-base))))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-trader tx-sender))
    (asserts! (> (var-get current-epoch-id) u0) ERR_EPOCH_ZERO_TRADING_ATTEMPT)
    (asserts! (is-standard counterparty-address) ERR_NOT_STANDARD)
    (asserts! (contract-call? .ht-hq-ststx-bull-v2 get-trading-allowed) ERR_TRADING_NOT_ALLOWED)
    (asserts! (>= (var-get trading-funds-available) funds-outgoing) ERR_NOT_ENOUGH_FUNDS_AVAILABLE)
    (asserts! (is-none (get block-height-settled current-epoch-info)) ERR_CURRENT_EPOCH_SETTLED)
    (asserts! (<= burn-block-height (+
      (get block-height-start current-epoch-info)
      (contract-call? .ht-hq-ststx-bull-v2 get-registration-window))) 
      ERR_REGISTRATION_WINDOW_CLOSED)
    (match (map-get? counterparty-info { address: counterparty-address })
      counterparty-data
        (map-set counterparty-info { address: counterparty-address }
          (merge
            counterparty-data
            {
              units-registered: amount,
              price-registered: (some option-price-per-underlying),
              block-height-registered: (some burn-block-height),
            }
          )
        )
        (map-set counterparty-info { address: counterparty-address }
          {                         
            units-registered: amount,
            price-registered: (some option-price-per-underlying),
            block-height-registered: (some burn-block-height),
            units-transacted: u0,
            funds-requested: none,
          }
        )
      )
    (var-set trading-funds-available (- (var-get trading-funds-available) funds-outgoing))
    (ok (var-set trading-funds-registered (+ (var-get trading-funds-registered) funds-outgoing)))))

;; @desc Updates a trade registration
;; @param new-amount: amount of units to be transacted
;; @param option-price-per-underlying: price of an option represeting one unit of underlying (for example 1 BTC)
;; @param counterparty: principal of the counterparty
(define-public (update-registration
  (new-amount uint)
  (new-option-price-per-underlying uint)
  (counterparty-address principal))
  (let (
    (counterparty-info-entry (try! (get-counterparty-info counterparty-address)))
    (current-units-registered (get units-registered counterparty-info-entry))
    (current-price-registered (unwrap! (get price-registered counterparty-info-entry) ERR_NO_REGISTRATION_FOR_COUNTERPARTY))
    (unit-size (contract-call? .ht-hq-ststx-bull-v2 get-unit-size))
    (current-funds-outgoing (* current-units-registered (/ (* current-price-registered unit-size) underlying-base)))
    (units-transacted (get units-transacted counterparty-info-entry))
    (new-funds-outgoing (* new-amount (/ (* new-option-price-per-underlying unit-size) underlying-base))))
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-trader tx-sender))
    (if (> new-funds-outgoing current-funds-outgoing)
      (asserts! (>= (var-get trading-funds-available) (- new-funds-outgoing current-funds-outgoing)) ERR_NOT_ENOUGH_FUNDS_AVAILABLE)
      true
    )
    (if (is-eq new-amount u0)
      (if (is-eq units-transacted u0) 
        (map-delete counterparty-info { address: counterparty-address })
        (map-set counterparty-info	{ address: counterparty-address }
          (merge
            counterparty-info-entry
            { 
              units-registered: new-amount,
              price-registered: none,
              block-height-registered: none,
            }
          )
        )
      )
      (map-set counterparty-info { address: counterparty-address }
        (merge
          counterparty-info-entry
          {               
            units-registered: new-amount,
            price-registered: (some new-option-price-per-underlying),
            block-height-registered: (some burn-block-height),
          }
        )
      )
    )
    (var-set trading-funds-registered (+ (- (var-get trading-funds-registered) current-funds-outgoing) new-funds-outgoing))
    (ok (var-set trading-funds-available (- (+ (var-get trading-funds-available) current-funds-outgoing) new-funds-outgoing)))))

;; @desc Allows a counterparty to confirm a trade and transfers the premium 
;; @param amount: amount of units to be transacted 
;; @param option-price-per-underlying: price of an option represeting one unit of underlying (for example 1 BTC)
(define-public (confirm-trade
  (amount uint)
  (option-price-per-underlying uint))
  (let (
    (counterparty tx-sender)
    (counterparty-info-entry (try! (get-counterparty-info tx-sender)))
    (price-registered (unwrap! (get price-registered counterparty-info-entry) ERR_NO_REGISTRATION_FOR_COUNTERPARTY))
    (block-height-registered (unwrap-panic (get block-height-registered counterparty-info-entry)))
    (funds-outgoing (/ (* (* option-price-per-underlying amount) (contract-call? .ht-hq-ststx-bull-v2 get-unit-size)) underlying-base))
    (current-epoch-info (unwrap-panic (get-current-epoch-info))))
    (asserts! (is-eq amount (get units-registered counterparty-info-entry)) ERR_AMOUNT_MISMATCH)
    (asserts! (is-eq option-price-per-underlying price-registered) ERR_PRICE_MISMATCH)
    (asserts! (<= burn-block-height (+ block-height-registered (contract-call? .ht-hq-ststx-bull-v2 get-confirmation-window))) ERR_CONFIRMATION_WINDOW_CLOSED)
    (try! (as-contract (contract-call? .ht-vault-ststx-bull-v2 payout-funds funds-outgoing counterparty)))
    (map-set counterparty-info { address: tx-sender }
      { 
        units-registered: u0,
        price-registered: none,
        block-height-registered: none,
        units-transacted: (+ amount (get units-transacted counterparty-info-entry)),
        funds-requested: none,
      }
    )
    (var-set trading-funds-registered (- (var-get trading-funds-registered) funds-outgoing))
    (if (is-none (index-of? (var-get counterparty-addresses) tx-sender))
      (var-set counterparty-addresses (unwrap-panic (as-max-len? (append (var-get counterparty-addresses) tx-sender) u5000)))
      true
    )
    (map-set epoch-info { epoch-id: (get-current-epoch-id) } 
      (merge
        current-epoch-info
        { 
          total-units-transacted: (+ (get total-units-transacted current-epoch-info) amount),
          total-premium: (+ (get total-premium current-epoch-info) funds-outgoing)
        }
      )
    )
    (ok true)))

;; @desc Allows a counterparty to make a payment for an ITM option
;; @param amount: amount to be paid in underlying
(define-public (make-payment
  (amount uint))
  (let (
    (funds-requested (unwrap! (get funds-requested (try! (get-counterparty-info tx-sender))) ERR_PNL_UNDETERMINED))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (block-height-pnl (unwrap-panic (get block-height-pnl current-epoch-info)))
    (payment-window (contract-call? .ht-hq-ststx-bull-v2 get-payment-window)))
    (asserts! (<= burn-block-height (+ block-height-pnl payment-window)) ERR_PAYMENT_WINDOW_CLOSED)
    (asserts! (is-eq amount funds-requested) ERR_PAYMENT_AMOUNTS_DONT_MATCH)
    (try! (contract-call? .ht-vault-ststx-bull-v2 deposit-funds amount tx-sender))
    (map-delete counterparty-info { address: tx-sender })
    (var-set helper-principal tx-sender)
    (var-set counterparty-addresses (filter remove-principal (var-get counterparty-addresses)))
    (var-set total-funds-requested (- (var-get total-funds-requested) amount))
    (if (is-eq (var-get total-funds-requested) u0)
      (try! (settle))
      true
    )
    (ok true)))

;; @desc Allows a counterparty to make a payment for an ITM option after the payment-window closed
;; @param amount: amount to be paid in underlying
(define-public (make-delayed-payment
  (amount uint))
  (let (
    (funds-requested (get funds-requested (try! (get-delinquent-counterparty-info tx-sender)))))
    (asserts! (is-eq amount funds-requested) ERR_PAYMENT_AMOUNTS_DONT_MATCH)
    (try! (contract-call? .ht-vault-ststx-bull-v2 deposit-funds amount tx-sender))
    (map-delete delinquent-counterparty-info { address: tx-sender })
    (var-set helper-principal tx-sender)
    (ok (var-set delinquent-counterparty-addresses (filter remove-principal (var-get delinquent-counterparty-addresses))))))

;;-------------------------------------
;; Helper 
;;-------------------------------------

;; @desc Deletes counterparty entry from counterparty-info
;; @param counterparty: principal that has sold option(s) to the contract
(define-private (delete-counterparty
  (counterparty-address principal))
  (ok (map-delete counterparty-info { address: counterparty-address })))

;; @desc Moves counterparty from counter-party-ledger to delinquent-counterparty-info
;; @param counterparty: principal that has sold option(s) to the contract
(define-private (add-delinquent-counterparty
  (counterparty-address principal))
  (let (
    (counterparty-info-entry (unwrap-panic (get-counterparty-info counterparty-address))) 
    (funds-requested (unwrap-panic (get funds-requested counterparty-info-entry)))
    (units-transacted (get units-transacted counterparty-info-entry)))
    (map-set delinquent-counterparty-info { address: counterparty-address }
      { 
        funds-requested: funds-requested,
        units-transacted: units-transacted,
        epoch-id: (get-current-epoch-id)
      }
    )
    (ok (var-set delinquent-counterparty-addresses (unwrap-panic (as-max-len? (append (var-get delinquent-counterparty-addresses) counterparty-address) u5000))))))

;; @desc Removes a principal from a list
;; @param list-item: principal that is checked against helper-principal
(define-private (remove-principal
  (list-item principal))
  (not (is-eq (var-get helper-principal) list-item)))

;; @desc Converts usd value to option-asset value (i.e. BTC)
;; @param usd: usd value
;; @usd-per-option-asset: usd price of option-asset (i.e. BTCUSD)
(define-read-only (usd-to-option-asset (usd uint) (usd-per-option-asset uint))
  (/ (* usd underlying-base) usd-per-option-asset))

;;-------------------------------------
;; Initialize 
;;-------------------------------------

;; @desc Allows admin to initialize the contract; can only be called once
;; @param first-expiry: unix timestamp in milliseconds of the first expiry
(define-public (initialize
  (first-expiry uint))
  (begin
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-admin tx-sender))
    (asserts! (not (var-get is-initialized)) ERR_ALREADY_INITIALIZED)
    (var-set is-initialized true) ;; Set to true so that this can't be called again
    (map-set epoch-info { epoch-id: u0 } 
      {
        epoch-expiry: first-expiry,
        strike-call: none,
        strike-put: none,
        barrier-up: none,
        barrier-down: none,
        unit-pnl: none,
        settlement-rate: none,
        block-height-start: burn-block-height,
        block-height-pnl: none,
        block-height-settled: none,
        total-units-transacted: u0,
        total-premium: u0,
        total-underlying-active: u0,
        underlying-per-token-settled: none,
        epoch-risk: (contract-call? .ht-hq-ststx-bull-v2 get-epoch-risk),
        unit-size: (contract-call? .ht-hq-ststx-bull-v2 get-unit-size)
      }
    )
    (ok true)))

;;-------------------------------------
;; Emergency API 
;;-------------------------------------

;; In case of a deliquent counterparty this function ensures that the current epoch can be settled and deposits and withdrawals are procssed

;; @desc Forces the epoch to settle if the payment-window has closed
(define-public (force-settle)
  (let (
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (block-height-pnl (unwrap! (get block-height-pnl current-epoch-info) ERR_PNL_UNDETERMINED)))
    (asserts! (> burn-block-height (+ block-height-pnl (contract-call? .ht-hq-ststx-bull-v2 get-payment-window))) ERR_PAYMENT_WINDOW_STILL_OPEN)
    (if (is-none (get block-height-settled current-epoch-info))
      (begin ;; block-height-settled == none
        (var-set delinquent-epoch true)
        (map add-delinquent-counterparty (var-get counterparty-addresses))
        (try! (settle))
      )
      (try! (activate-pending-claims))
    )
    (ok true)))

;; In case of malfuntion or compromise of determine-pnl this function ensures that a pnl is set so that settle can be called

;; @desc Forces sets pnl if the pnl-calculation-window has closed
;; @param use-block-height: if true, use block-height to determine pnl-calculation-window instead of oracle timestamp
;; @price-feed-bytes: pyth price feed data
;; @execution-plan: pyth contracts to be called by oracle
(define-public (force-determine-pnl
  (use-block-height bool)
  (price-feed-bytes (buff 8192))
  (execution-plan {
    pyth-storage-contract: <pyth-storage-trait>,
    pyth-decoder-contract: <pyth-decoder-trait>,
    wormhole-core-contract: <wormhole-core-trait>
  }))
  (let (
    (epoch-id (get-current-epoch-id))
    (current-epoch-info (unwrap-panic (get-current-epoch-info)))
    (current-epoch-expiry (get epoch-expiry current-epoch-info))
    (pnl-calculation-window (contract-call? .ht-hq-ststx-bull-v2 get-pnl-calculation-window)))
    (asserts! (is-none (get block-height-pnl current-epoch-info)) ERR_PNL_ALREADY_DETERMINED)
    (if use-block-height
      (let (
        (previous-epoch-expiry (get epoch-expiry (try! (get-epoch-info (- epoch-id u1)))))
        (expected-epoch-duration-in-blocks (/ (/ (- current-epoch-expiry previous-epoch-expiry) minute-in-ms) u10))
        (current-block-height-start (get block-height-start current-epoch-info))
        (pnl-calculation-window-in-blocks (/ (/ pnl-calculation-window minute-in-ms) u10))
        (safety-margin (* u5 u144))) ;; 5 days worth of blocks
        (asserts! (>= burn-block-height (+ (+ (+ current-block-height-start expected-epoch-duration-in-blocks) pnl-calculation-window-in-blocks) safety-margin)) ERR_PNL_CALCULATION_WINDOW_STILL_OPEN))
      (let (
        (decoded-prices (try! (contract-call? 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-oracle-v2 decode-price-feeds price-feed-bytes execution-plan)))
        (decoded-price (element-at decoded-prices u0))
        (timestamp (* (unwrap-panic (get publish-time decoded-price)) u1000)))
        (asserts! (>= timestamp (+ current-epoch-expiry pnl-calculation-window)) ERR_PNL_CALCULATION_WINDOW_STILL_OPEN)
      )
    )
    (map-set epoch-info { epoch-id: epoch-id } 
      (merge
        current-epoch-info
        { 
          unit-pnl: (some u0),
          settlement-rate: (some u0),
          block-height-pnl: (some burn-block-height)
        }
      )
    )
    (var-set delinquent-epoch true) ;; set true so no fees are charged
    (try! (settle))
    (ok true)))

;; In case of contract malfunction these function ensure all funds in the contract can be recovered

;; @desc Forces all queued-deposits to be processed
(define-public (force-activate-pending-deposit-claims)
  (begin
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-admin tx-sender))
    (as-contract (contract-call? .ht-vault-ststx-bull-v2 activate-pending-deposit-claims))))

;; @desc Forces all queued-withdrawals to be processed
(define-public (force-activate-pending-withdrawal-claims)
  (begin
    (try! (contract-call? .ht-hq-ststx-bull-v2 check-is-admin tx-sender))
    (as-contract (contract-call? .ht-vault-ststx-bull-v2 activate-pending-withdrawal-claims))))

Functions (33)

FunctionAccessArgs
get-current-epoch-inforead-only
get-counterparty-inforead-onlycounterparty: principal
get-counterparty-addressesread-only
get-delinquent-counterparty-inforead-onlycounterparty: principal
get-delinquent-counterparty-addressesread-only
settleprivate
charge-feesprivate
settle-epoch-infoprivate
activate-pending-claimsprivate
init-trading-funds-availableprivate
reset-dataprivate
payment-requesterprivatecounterparty-address: principal
determine-pnlpublicpnl-calculator-contract: <pnl-calculator-trait>, use-pnl-calculator: bool, pnl-usd-set: (optional uint
start-new-epochpublicpnl-calculator-contract: <pnl-calculator-trait>, expiry: uint, strike-call: (optional uint
register-tradepublicamount: uint, option-price-per-underlying: uint, counterparty-address: principal
update-registrationpublicnew-amount: uint, new-option-price-per-underlying: uint, counterparty-address: principal
confirm-tradepublicamount: uint, option-price-per-underlying: uint
make-paymentpublicamount: uint
make-delayed-paymentpublicamount: uint
delete-counterpartyprivatecounterparty-address: principal
add-delinquent-counterpartyprivatecounterparty-address: principal
remove-principalprivatelist-item: principal
usd-to-option-assetread-onlyusd: uint, usd-per-option-asset: uint
initializepublicfirst-expiry: uint
force-settlepublic
force-determine-pnlpublicuse-block-height: bool, price-feed-bytes: (buff 8192
force-activate-pending-deposit-claimspublic
force-activate-pending-withdrawal-claimspublic
get-current-epoch-idread-only
get-trading-funds-availableread-only
get-trading-funds-registeredread-only
get-total-funds-requestedread-only
get-epoch-inforead-onlyepoch-id: uint