Source Code

;; @contract Controller
;; @version 1

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

(define-constant ERR_ZERO_ONLY_POSITIVE (err u105001))

(define-constant bps-base (pow u10 u4))
(define-constant hbtc-base (pow u10 u8))

;;-------------------------------------
;; Rewarder
;;-------------------------------------

;; @desc - log the reward and update the token price
;; @param amount - the amount of rewards for the interval (in deposit-asset, 10**8)
;; @param is-positive - whether the reward is positive or negative  
;; @note - if amount is zero, is-positive must be true
(define-public (log-reward (amount uint) (is-positive bool))
  (let (
    (token-price (contract-call? .test-state-hbtc-v1 get-token-price))
    (supply (unwrap-panic (contract-call? .test-token-hbtc get-total-supply)))
    (vault-balance (/ (* token-price supply) hbtc-base))
    (rf-balance (unwrap-panic (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token get-balance .test-reserve-fund-hbtc-v1)))
    (fees (contract-call? .test-state-hbtc-v1 get-fees))
    (fee-address (contract-call? .test-state-hbtc-v1 get-fee-address))
    (perf-fee (if is-positive (/ (* (get perf-fee fees) amount) bps-base) u0))
    (mgmt-fee (/ (* (get mgmt-fee fees) vault-balance) bps-base u100))
    (total-fees (+ perf-fee mgmt-fee))
    (is-profit (if (and is-positive (>= amount total-fees)) true false))
  )
    (try! (contract-call? .test-hq-vaults-v1 check-is-protocol-enabled))
    (try! (contract-call? .test-hq-vaults-v1 check-is-rewarder contract-caller))
    (try! (contract-call? .test-state-hbtc-v1 check-is-update-window-open))
    (try! (contract-call? .test-state-hbtc-v1 check-max-reward amount vault-balance))

    (asserts! (if (is-eq amount u0) (is-eq is-positive true) true) ERR_ZERO_ONLY_POSITIVE)

    (if is-profit
      ;; Handle profit and zero scenario -> token price increases
      (try! (handle-profit amount perf-fee mgmt-fee total-fees rf-balance token-price vault-balance fee-address))
      
      ;; Handle loss scenarios
      (let (
        (rf-required (if is-positive (- mgmt-fee amount) (+ amount mgmt-fee)))
      )
        (if (<= rf-required rf-balance)
          ;; Reserve-fund can cover the loss -> token price does not change
          (try! (handle-loss-covered amount mgmt-fee rf-balance token-price vault-balance fee-address))

          ;; Reserve-fund cannot cover the loss -> token price decreases
          (try! (handle-loss-exceeds amount mgmt-fee rf-required rf-balance token-price vault-balance fee-address))
        )
      )
    )
    (ok (try! (contract-call? .test-state-hbtc-v1 update-last-log-ts)))
  )
)

;;-------------------------------------
;; Helper Functions
;;-------------------------------------

;; @desc - Handle profit scenario
;; @param amount - original reward amount
;; @param perf-fee - performance fee
;; @param mgmt-fee - management fee
;; @param rf-balance - current reserve fund balance
;; @param total-fees - total fees
;; @param token-price - current token price
;; @param vault-balance - current vault balance
;; @param fee-address - fee recipient address
(define-private (handle-profit (amount uint) (perf-fee uint) (mgmt-fee uint) (total-fees uint) (rf-balance uint) (token-price uint) (vault-balance uint) (fee-address principal))
  (let (
    (net-amount (- amount total-fees))
    (rf-amount (/ (* net-amount (contract-call? .test-state-hbtc-v1 get-reserve-rate)) bps-base))
    (reward-amount (- net-amount rf-amount))
    (new-token-price (/ (* token-price (+ hbtc-base (/ (* reward-amount hbtc-base) vault-balance))) hbtc-base))
    (case (if (is-eq net-amount u0) "zero" "profit"))
    (new-rf-balance (+ rf-balance rf-amount))
    (return-percent-of-bps (/ (* reward-amount bps-base u100) vault-balance))
  )

    (print-event case contract-caller
      (build-print-data amount perf-fee mgmt-fee rf-amount reward-amount rf-balance new-rf-balance token-price new-token-price vault-balance return-percent-of-bps)
    )
    
    (try! (transfer-fees true perf-fee mgmt-fee fee-address))
    (if (> rf-amount u0)
      (try! (contract-call? .test-reserve-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token rf-amount .test-reserve-fund-hbtc-v1))
      true)
    (ok (try! (contract-call? .test-state-hbtc-v1 set-token-price new-token-price)))
  )
)

;; @desc - Handle loss covered by reserve fund
;; @param amount - original reward amount (in deposit-asset, 10**8)
;; @param mgmt-fee - management fee
;; @param rf-balance - current reserve fund balance
;; @param token-price - current token price
;; @param vault-balance - current vault balance
;; @param fee-address - fee recipient address
(define-private (handle-loss-covered (amount uint) (mgmt-fee uint) (rf-balance uint) (token-price uint) (vault-balance uint) (fee-address principal))
  (let (
    (new-rf-balance (- rf-balance amount mgmt-fee))
    (return-percent-of-bps u0)
  )

    (print-event "loss-covered" contract-caller 
      (build-print-data amount u0 mgmt-fee u0 u0 rf-balance new-rf-balance token-price token-price vault-balance return-percent-of-bps)
    )
    
    (try! (transfer-fees false u0 mgmt-fee fee-address))
    (ok (try! (contract-call? .test-reserve-fund-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token amount .test-reserve-hbtc-v1 none))) 
  )
)

;; @desc - Handle loss exceeding reserve fund
;; @param amount - original reward amount
;; @param mgmt-fee - management fee
;; @param rf-required - amount after fees
;; @param rf-balance - current reserve fund balance
;; @param token-price - current token price
;; @param vault-balance - current vault balance
;; @param fee-address - fee recipient address
(define-private (handle-loss-exceeds (amount uint) (mgmt-fee uint) (rf-required uint) (rf-balance uint) (token-price uint) (vault-balance uint) (fee-address principal))
  (let (
    (loss-amount (- rf-required rf-balance))
    (new-token-price (/ (* token-price (- hbtc-base (/ (* loss-amount hbtc-base) vault-balance))) hbtc-base))
    (return-percent-of-bps (/ (* loss-amount bps-base u100) vault-balance))
  )

    (print-event "loss-exceeds" contract-caller 
      (build-print-data amount u0 mgmt-fee u0 u0 rf-balance u0 token-price new-token-price vault-balance return-percent-of-bps)
    )

    (try! (transfer-fees true u0 mgmt-fee fee-address))
    (try! (contract-call? .test-reserve-fund-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token rf-balance .test-reserve-hbtc-v1 none))
    (ok (try! (contract-call? .test-state-hbtc-v1 set-token-price new-token-price)))
  )
)

;; @desc - Transfer fees to the fee address
;; @param perf-fee - performance fee amount
;; @param mgmt-fee - management fee amount
;; @param fee-address - address to receive fees
(define-private (transfer-fees (from-reserve bool) (perf-fee uint) (mgmt-fee uint) (fee-address principal))
  (begin
    (if from-reserve
      (begin
        (if (> perf-fee u0)
          (try! (contract-call? .test-reserve-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token perf-fee fee-address))
          true)
        (if (> mgmt-fee u0)
          (try! (contract-call? .test-reserve-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token mgmt-fee fee-address))
          true)
      )
      (begin ;; from reserve-fund
        (if (> perf-fee u0)
          (try! (contract-call? .test-reserve-fund-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token perf-fee fee-address none))
          true)
        (if (> mgmt-fee u0)
          (try! (contract-call? .test-reserve-fund-hbtc-v1 transfer 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token mgmt-fee fee-address none))
          true)
      )
    )
    (ok true)
  )
)

;;-------------------------------------
;; Print Functions
;;-------------------------------------

;; @desc - Log reward event with unified data structure
;; @param case - the reward case (profit, zero-profit, negative-within-reserve, negative-exceeds-reserve)
;; @param data - unified log data structure
;; @param user - the caller principal
(define-private (print-event (case (string-ascii 24)) (user principal) (data { amount: uint, perf-fee: uint, mgmt-fee: uint, rf-amount: uint, reward-amount: uint, old-rf-balance: uint, new-rf-balance: uint, old-token-price: uint, new-token-price: uint, vault-balance: uint, return-percent-of-bps: uint }))
  (print { 
    action: "log-reward", 
    case: case, 
    user: user, 
    data: data,
  })
)

;; @desc - Build unified log data for all reward scenarios
(define-private (build-print-data (amount uint) (perf-fee uint) (mgmt-fee uint) (rf-amount uint) (reward-amount uint) (old-rf-balance uint) (new-rf-balance uint) (old-token-price uint) (new-token-price uint) (vault-balance uint) (return-percent-of-bps uint))
  {
    amount: amount,
    perf-fee: perf-fee,
    mgmt-fee: mgmt-fee,
    rf-amount: rf-amount,
    reward-amount: reward-amount,
    old-rf-balance: old-rf-balance,
    new-rf-balance: new-rf-balance,
    old-token-price: old-token-price,
    new-token-price: new-token-price,
    vault-balance: vault-balance,
    return-percent-of-bps: return-percent-of-bps
  }
)

Functions (7)

FunctionAccessArgs
log-rewardpublicamount: uint, is-positive: bool
handle-profitprivateamount: uint, perf-fee: uint, mgmt-fee: uint, total-fees: uint, rf-balance: uint, token-price: uint, vault-balance: uint, fee-address: principal
handle-loss-coveredprivateamount: uint, mgmt-fee: uint, rf-balance: uint, token-price: uint, vault-balance: uint, fee-address: principal
handle-loss-exceedsprivateamount: uint, mgmt-fee: uint, rf-required: uint, rf-balance: uint, token-price: uint, vault-balance: uint, fee-address: principal
transfer-feesprivatefrom-reserve: bool, perf-fee: uint, mgmt-fee: uint, fee-address: principal
print-eventprivatecase: (string-ascii 24
build-print-dataprivateamount: uint, perf-fee: uint, mgmt-fee: uint, rf-amount: uint, reward-amount: uint, old-rf-balance: uint, new-rf-balance: uint, old-token-price: uint, new-token-price: uint, vault-balance: uint, return-percent-of-bps: uint