Source Code

;; SPDX-License-Identifier: BUSL-1.1
;; TITLE: kinked-interest-rate-module
;; VERSION: 1.0

  
;; ERROR VALUES
(define-constant SUCCESS (ok true))
(define-constant ERR-CONTRACT-ALREADY-INITIATED (err u70000))
(define-constant ERR-NEED-TO-INIT-FROM-LAUNCH-PRINCIPAL (err u70001))
(define-constant ERR-NOT-INITIALIZED (err u70002))
(define-constant ERR-NOT-GOVERNANCE (err u70003))
(define-constant ERR-INVALID-UTILIZATION-KINK (err u70004))

;; CONSTANTS
(define-constant one-8 u100000000)
(define-constant one-12 u1000000000000)
(define-constant fact_2 u2000000000000)
(define-constant fact_3 u6000000000000)
(define-constant fact_4 u24000000000000)
(define-constant fact_5 u120000000000000)
(define-constant fact_6 u720000000000000)
(define-constant seconds-in-year u31536000)
(define-constant STACKS_BLOCK_TIME (contract-call? .constants-v1 get-stacks-block-time ))

;; DATA-VARS 
(define-constant contract-deployer contract-caller)
(define-data-var is-initialized bool false)
(define-data-var ir-slope-1 uint u0)
(define-data-var ir-slope-2 uint u0)
(define-data-var utilization-kink uint u0)
(define-data-var base-ir uint u0) ;; interest when utilization is 0


;; PUBLIC FUNCTIONS 
(define-public (update-ir-params (ir-slope-1-val uint) (ir-slope-2-val uint) (utilization-kink-val uint) (base-ir-val uint))
  (begin 
    ;; guard clauses
    (if (not (var-get is-initialized)) 
      (begin
        (asserts! (is-eq contract-caller contract-deployer) ERR-NEED-TO-INIT-FROM-LAUNCH-PRINCIPAL)
        (var-set is-initialized true)
      )
      (begin
        (asserts! (not (is-eq contract-caller contract-deployer)) ERR-CONTRACT-ALREADY-INITIATED)
        (asserts! (is-eq contract-caller (contract-call? .state-v1 get-governance)) ERR-NOT-GOVERNANCE)
      )
    )

    (asserts! (< utilization-kink-val one-12) ERR-INVALID-UTILIZATION-KINK)
    (print {
        old-ir-slope-1: (var-get ir-slope-1),
        new-ir-slope-1: ir-slope-1-val,
        old-ir-slope-2: (var-get ir-slope-2),
        new-ir-slope-2: ir-slope-2-val,
        old-utilization-kink: (var-get utilization-kink),
        new-utilization-kink: utilization-kink-val,
        old-base-ir: (var-get base-ir),
        new-base-ir: base-ir-val,
        user: contract-caller,
        action: "update-ir-params"
    })

    ;; set data-vars
    (var-set ir-slope-1 ir-slope-1-val)
    (var-set ir-slope-2 ir-slope-2-val)
    (var-set utilization-kink utilization-kink-val)
    (var-set base-ir base-ir-val)

    ;; return val
    SUCCESS
))

;; READ-ONLY FUNCTIONS 

;; Accrues interest based on the the last accrued block
;; returns updated lp interest, staked interest, protocol interest, total assets, and last accrued block
(define-read-only (accrue-interest (last-accrued-block-time uint) (lp-open-interest uint) (staked-open-interest uint) (staking-reward-percentage uint) (protocol-open-interest uint) (protocol-reserve-percentage uint) (total-assets uint))
  (let (
    (time-now (+ (unwrap-panic (get-stacks-block-info? time (- stacks-block-height u1))) STACKS_BLOCK_TIME))
    (elapsed-block-time (- time-now last-accrued-block-time))
    (premature-return (asserts!
      (not (or (is-eq u0 elapsed-block-time) (not (contract-call? .state-v1 is-interest-accrual-enabled))))
      (ok {
        last-accrued-block-time: last-accrued-block-time,
        lp-open-interest: lp-open-interest,
        protocol-open-interest: protocol-open-interest,
        staked-open-interest: staked-open-interest,
        total-assets: total-assets
      })))
    (open-interest (+ (+ lp-open-interest protocol-open-interest) staked-open-interest))
    (interest-rate-per-block (try! (get-ir total-assets open-interest)))
    (compounded-interest-rate (try! (compounded-interest interest-rate-per-block elapsed-block-time)))
    (total-interest (calc-total-interest compounded-interest-rate open-interest))
    (protocol-interest (/ (* total-interest protocol-reserve-percentage) one-8))
    (lp-interest (- total-interest protocol-interest))
    (staked-interest (/ (* lp-interest staking-reward-percentage) one-8))
  )
    (print {
        interest-rate-per-block: interest-rate-per-block,
        compounded-interest-rate: compounded-interest-rate,
        total-interest: total-interest,
        lp-open-interest: lp-interest,
        protocol-open-interest: protocol-interest,
      })
    (ok {
      last-accrued-block-time: time-now,
      lp-open-interest: (+ lp-open-interest (- lp-interest staked-interest)),
      staked-open-interest: (+ staked-open-interest staked-interest),
      protocol-open-interest: (+ protocol-open-interest protocol-interest),
      total-assets: (+ total-assets lp-interest),
    })
  )
)

;; total-assets and open-interest are fixed to u8 precision
(define-read-only (utilization-calc (total-assets uint) (open-interest uint))
  (if (> (+ total-assets open-interest) u0) (/ (* open-interest one-12) total-assets) u0)
)

(define-read-only (get-ir (total-assets uint) (open-interest uint))
  (begin
  	(asserts! (var-get is-initialized) ERR-NOT-INITIALIZED)
  	(ok (ir-calc (utilization-calc total-assets open-interest)))
))

(define-read-only (compounded-interest (current-interest-rate uint) (elapsed-block-time uint))
  (begin
    (asserts! (var-get is-initialized) ERR-NOT-INITIALIZED)
    (ok (taylor-6 (get-rt-by-block current-interest-rate elapsed-block-time)))
))

(define-read-only (get-ir-params)
  {
    ir-slope-1: (var-get ir-slope-1),
    ir-slope-2: (var-get ir-slope-2),
    utilization-kink: (var-get utilization-kink),
    base-ir: (var-get base-ir)
})
  
;; PRIVATE HELPER FUNCTIONS 
(define-read-only (calc-total-interest (ir uint) (open-ir uint))
  (let ((interest-factor (- ir one-12))
    (total-interest (contract-call? .math-v1 divide-round-up (* open-ir interest-factor) one-12)))
    total-interest
))

(define-private (interest-util-less-than-kink (util-with-res uint)) 
  (+ (/ (* (var-get ir-slope-1) util-with-res) one-12) (var-get base-ir))
)

(define-private (interest-util-geq-kink (util-with-res uint)) 
  (+ 
    (/
      (+ 
        (* (var-get ir-slope-2) (- util-with-res (var-get utilization-kink))) 
        (* (var-get ir-slope-1) (var-get utilization-kink))
      )
      one-12
    ) 
    (var-get base-ir)
))

(define-private (ir-calc (util-with-res uint))
  (if (is-eq util-with-res u0)
    u0
    (if
      (>= util-with-res (var-get utilization-kink))
        (interest-util-geq-kink util-with-res)
        (interest-util-less-than-kink util-with-res)
    )
))

(define-private (mul (x uint) (y uint))
	(/ (+ (* x y) (/ one-12 u2)) one-12)
)

(define-private (div (x uint) (y uint))
	(/ (+ (* x one-12) (/ y u2)) y)
)

;; rate in 12-fixed
;; n-blocks
(define-read-only (get-rt-by-block (rate uint) (elapsed-block-time uint))
  (/ (* rate (/ (* elapsed-block-time one-12) seconds-in-year)) one-12)
)

;; taylor series expansion to the 6th degree to estimate e^x
(define-read-only (taylor-6 (x uint))
  (let (
      (x_2 (mul x x))
      (x_3 (mul x x_2))
      (x_4 (mul x x_3))
      (x_5 (mul x x_4))
      (x_6 (mul x x_5))
    )
    (+
      one-12 
      x
      (div x_2 fact_2)
      (div x_3 fact_3)
      (div x_4 fact_4)
      (div x_5 fact_5)
      (div x_6 fact_6)
    )
))

(update-ir-params u40000000000 u3100000000000 u850000000000 u5000000000)

Functions (14)

FunctionAccessArgs
update-ir-paramspublicir-slope-1-val: uint, ir-slope-2-val: uint, utilization-kink-val: uint, base-ir-val: uint
accrue-interestread-onlylast-accrued-block-time: uint, lp-open-interest: uint, staked-open-interest: uint, staking-reward-percentage: uint, protocol-open-interest: uint, protocol-reserve-percentage: uint, total-assets: uint
utilization-calcread-onlytotal-assets: uint, open-interest: uint
get-irread-onlytotal-assets: uint, open-interest: uint
compounded-interestread-onlycurrent-interest-rate: uint, elapsed-block-time: uint
get-ir-paramsread-only
calc-total-interestread-onlyir: uint, open-ir: uint
interest-util-less-than-kinkprivateutil-with-res: uint
interest-util-geq-kinkprivateutil-with-res: uint
ir-calcprivateutil-with-res: uint
mulprivatex: uint, y: uint
divprivatex: uint, y: uint
get-rt-by-blockread-onlyrate: uint, elapsed-block-time: uint
taylor-6read-onlyx: uint