Source Code

;; @contract Bond Depository
;; @version 1.1

(use-trait ft-trait .sip-010-trait-ft-standard.sip-010-trait)
(use-trait treasury-trait .treasury-trait-v1-1.treasury-trait)
(use-trait staking-distributor-trait .staking-distributor-trait-v1-1.staking-distributor-trait)
(use-trait staking-trait .staking-trait-v1-1.staking-trait)
(use-trait bond-teller-trait .bond-teller-trait-v1-1.bond-teller-trait)
(use-trait value-calculator-trait .value-calculator-trait-v1-1.value-calculator-trait)

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

(define-constant ERR-NOT-AUTHORIZED u3203001)

(define-constant ERR-WRONG-TREASURY u3202001)
(define-constant ERR-WRONG-TOKEN u3202002)
(define-constant ERR-WRONG-BOND-TELLER u3202003)
(define-constant ERR-WRONG-VALUE-CALCULATOR u3202004)

(define-constant ERR-BOND-CONCLUDED u3200001)
(define-constant ERR-BOND-CAPACITY-REACHED u3200002)
(define-constant ERR-BOND-TOO-LARGE u3200003)
(define-constant ERR-SLIPPAGE-TOO-LARGE u3200004)

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

(define-data-var active-bond-teller principal .bond-teller-v1-1)
(define-data-var active-treasury principal .treasury-v1-1)

(define-data-var bond-counter uint u0)

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

(define-map bond-types
  { bond-id: uint }
  {
    ;; Type
    token-address: principal,     ;; token to accept as payment
    start-capacity: uint,         ;; start capacity
    capacity: uint,               ;; remaining capacity
    capacity-is-payout: bool,     ;; capacity limit for payout or principal
    total-debt: uint,             ;; total debt from bond
    last-decay: uint,             ;; last block when debt was decayed

    ;; Terms
    control-variable: uint,       ;; scaling variable for price
    fixed-term: bool,             ;; fixed term or fixed expiration
    vesting-term: uint,           ;; fixed-term - term in blocks
    expiration: uint,             ;; fixed-expiration - block number bond matures
    conclusion: uint,             ;; block number bond no longer offered
    minimum-price: uint,          ;; vs principal value
    max-payout: uint,             ;; in thousandths of a %. i.e. 500 = 0.5%
    max-debt: uint                ;; 9 decimal debt ratio, max % total supply created as debt
  }
)

(define-read-only (get-bond-type (bond-id uint))
  (map-get? bond-types { bond-id: bond-id })
)

;; ------------------------------------------
;; Var & Map Helpers
;; ------------------------------------------

(define-read-only (get-active-bond-teller)
  (var-get active-bond-teller)
)

(define-read-only (get-active-treasury)
  (var-get active-treasury)
)

(define-read-only (get-bond-counter)
  (var-get bond-counter)
)

;; ------------------------------------------
;; Deposit
;; ------------------------------------------

(define-public (deposit 
  (bond-teller <bond-teller-trait>) 
  (value-calculator <value-calculator-trait>) 
  (distributor <staking-distributor-trait>) 
  (treasury <treasury-trait>) 
  (staking <staking-trait>) 
  (token <ft-trait>) 
  (bond-id uint) 
  (amount uint) 
  (max-price uint)
  )
  (let (
    ;; lower debt first
    (decay-result (decay-debt bond-id))

    (bonder tx-sender)
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (capacity (get capacity bond-type))

    (token-value (unwrap-panic (contract-call? treasury get-token-value token value-calculator amount)))
    (payout (unwrap-panic (get-payout-for token-value bond-id)))
    (max-payout (unwrap-panic (get-max-payout bond-id)))
    (bond-price (unwrap-panic (get-bond-price bond-id)))
    (expiration (unwrap-panic (get-deposit-expiration bond-id)))
  )
    (asserts! (is-eq (contract-of bond-teller) (var-get active-bond-teller)) (err ERR-WRONG-BOND-TELLER))
    (asserts! (is-eq (contract-of treasury) (var-get active-treasury)) (err ERR-WRONG-TREASURY))
    (asserts! (is-eq (contract-of token) (get token-address bond-type)) (err ERR-WRONG-TOKEN))
    (asserts! (not (is-eq token-value u0)) (err ERR-WRONG-VALUE-CALCULATOR))

    (asserts! (< block-height (get conclusion bond-type)) (err ERR-BOND-CONCLUDED))
    (asserts! (<= payout max-payout) (err ERR-BOND-TOO-LARGE))
    (asserts! (>= max-price bond-price) (err ERR-SLIPPAGE-TOO-LARGE))

    ;; ensure there is remaining capacity for bond
    (if (get capacity-is-payout bond-type)
      (begin
        (asserts! (>= capacity payout) (err ERR-BOND-CAPACITY-REACHED))

        ;; set capacity and increase total debt
        (map-set bond-types { bond-id: bond-id } (merge bond-type { 
          capacity: (- capacity payout), 
          total-debt: (+ (get total-debt bond-type) token-value) 
        }))
      )
      (begin
        (asserts! (>= capacity amount) (err ERR-BOND-CAPACITY-REACHED))

        ;; set capacity and increase total debt
        (map-set bond-types { bond-id: bond-id } (merge bond-type { 
          capacity: (- capacity amount), 
          total-debt: (+ (get total-debt bond-type) token-value) 
        }))
      )
    )

    ;; send deposit to treasury
    (try! (contract-call? token transfer amount tx-sender (contract-of treasury) none))

    ;; user info stored with teller 
    (try! (as-contract (contract-call? bond-teller new-bond distributor treasury staking bond-id bonder (contract-of token) amount payout expiration)))

    ;; audit treasury reserve token
    (try! (contract-call? treasury audit-reserve-token token value-calculator))

    (ok payout)
  )
)

(define-read-only (get-deposit-expiration (bond-id uint))
  (let (
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (bond-fixed-term (get fixed-term bond-type))
  )
    (if (is-eq bond-fixed-term true)
      (ok (get expiration bond-type))
      (ok (+ (get vesting-term bond-type) block-height))
    )
  )
)

(define-private (decay-debt (bond-id uint))
  (let (
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (debt-decay (unwrap-panic (get-debt-decay bond-id)))
    (new-total-debt (- (get total-debt bond-type) debt-decay))
  )
    (map-set bond-types { bond-id: bond-id} (merge bond-type { total-debt: new-total-debt, last-decay: block-height }))
  )
)

;; ------------------------------------------
;; Payout
;; ------------------------------------------

;; determine maximum bond size
(define-read-only (get-max-payout (bond-id uint))
  (let (
    (total-supply (unwrap-panic (contract-call? .lydian-token get-total-supply)))
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
  )
    (ok (/ (* total-supply (get max-payout bond-type)) u100000))
  )
)

;; payout due for amount of treasury value
(define-read-only (get-payout-for (token-value uint) (bond-id uint))
  (let (
    (bond-price (unwrap-panic (get-bond-price bond-id)))
  )
    (ok (/ (/ (* token-value u100000000) bond-price) u1000000))
  )
)

;; ------------------------------------------
;; Bond price
;; ------------------------------------------

;; calculate current bond premium
(define-read-only (get-bond-price (bond-id uint))
  (let (
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (minimum-price (get minimum-price bond-type))
    (control-variable (get control-variable bond-type))
    (debt-ratio (unwrap-panic (get-debt-ratio bond-id)))
    (price (/ (+ (* control-variable debt-ratio) u1000000) u10000))
  )
    (if (< price minimum-price)
      (ok minimum-price)
      (ok price)
    )
  )
)

;; ------------------------------------------
;; Debt
;; ------------------------------------------

;; calculate current ratio of debt to LDN supply
(define-read-only (get-debt-ratio (bond-id uint))
  (let (
    (total-supply (unwrap-panic (contract-call? .lydian-token get-total-supply)))
    (current-debt (unwrap-panic (get-current-debt bond-id)))
  )
    (if (is-eq total-supply u0)
      (ok u0)
      (ok (/ (* current-debt u1000000) total-supply))
    )
  )
)

;; calculate debt factoring in decay
(define-read-only (get-current-debt (bond-id uint))
  (let (
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (total-debt (get total-debt bond-type))
    (debt-decay (unwrap-panic (get-debt-decay bond-id)))
  )
    (ok (- total-debt debt-decay))
  )
)

;; amount to decay total debt by
(define-read-only (get-debt-decay (bond-id uint))
  (let (
    (bond-type (unwrap-panic (map-get? bond-types { bond-id: bond-id })))
    (total-debt (get total-debt bond-type))
    (last-decay (get last-decay bond-type))
    (vesting-term (get vesting-term bond-type))
    (blocks-since-last (- block-height last-decay))
    (decay (/ (* total-debt blocks-since-last) vesting-term))
  )
    (if (> decay total-debt)
      (ok total-debt)
      (ok decay)
    )
  )
)

;; ---------------------------------------------------------
;; Admin
;; ---------------------------------------------------------

(define-public (add-bond 
    (token-address principal) 
    (capacity uint)
    (capacity-is-payout bool)

    (control-variable uint)
    (fixed-term bool)
    (vesting-term uint)
    (expiration uint)
    (conclusion uint)
    (minimum-price uint)
    (max-payout uint)
    (max-debt uint)
  )
  (let (
    (bond-id (var-get bond-counter))
  )
    (asserts! (is-eq tx-sender .lydian-dao) (err  ERR-NOT-AUTHORIZED))

    (map-set bond-types 
      { bond-id: bond-id } 
      { 
        token-address: token-address,
        start-capacity: capacity, 
        capacity: capacity,
        capacity-is-payout: capacity-is-payout,
        total-debt: u0,
        last-decay: block-height,

        control-variable: control-variable,
        fixed-term: fixed-term,
        vesting-term: vesting-term,
        expiration: expiration,
        conclusion: conclusion,
        minimum-price: minimum-price,
        max-payout: max-payout,
        max-debt: max-debt
      }
    )
    (var-set bond-counter (+ bond-id u1))
    (ok bond-id)
  )
)

(define-public (update-bond 
    (bond-id uint)
    (token-address principal) 
    (capacity uint)
    (capacity-is-payout bool)
    (total-debt uint)
    (last-decay uint)

    (control-variable uint)
    (fixed-term bool)
    (vesting-term uint)
    (expiration uint)
    (conclusion uint)
    (minimum-price uint)
    (max-payout uint)
    (max-debt uint)
  )
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err  ERR-NOT-AUTHORIZED))

    (map-set bond-types 
      { bond-id: bond-id } 
      { 
        token-address: token-address, 
        start-capacity: capacity,
        capacity: capacity,
        capacity-is-payout: capacity-is-payout,
        total-debt: total-debt,
        last-decay: last-decay,

        control-variable: control-variable,
        fixed-term: fixed-term,
        vesting-term: vesting-term,
        expiration: expiration,
        conclusion: conclusion,
        minimum-price: minimum-price,
        max-payout: max-payout,
        max-debt: max-debt
      }
    )
    (ok bond-id)
  )
)

(define-public (set-active-bond-teller (bond-teller principal))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err  ERR-NOT-AUTHORIZED))

    (var-set active-bond-teller bond-teller)
    (ok true)
  )
)

(define-public (set-active-treasury (treasury principal))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err  ERR-NOT-AUTHORIZED))

    (var-set active-treasury treasury)
    (ok true)
  )
)

Functions (14)

FunctionAccessArgs
get-bond-typeread-onlybond-id: uint
get-active-bond-tellerread-only
get-active-treasuryread-only
get-bond-counterread-only
get-deposit-expirationread-onlybond-id: uint
decay-debtprivatebond-id: uint
get-max-payoutread-onlybond-id: uint
get-payout-forread-onlytoken-value: uint, bond-id: uint
get-bond-priceread-onlybond-id: uint
get-debt-ratioread-onlybond-id: uint
get-current-debtread-onlybond-id: uint
get-debt-decayread-onlybond-id: uint
set-active-bond-tellerpublicbond-teller: principal
set-active-treasurypublictreasury: principal