Source Code

;; token math
;; ==========
;; * base/quote tokens may have different decimals
;; * lift -> calculate -> lower
;; * assume prices are unit prices (implicitly /one)
;; * outputs should just work with native repr we use for
;;   reserves/collateral/interest

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; x = lower(lift(x))
(define-read-only
  (lift
   (tokens {base: uint, quote: uint})
   (ctx    {base-decimals: uint, quote-decimals: uint, price: uint}))
  (let ((bd (get base-decimals  ctx))
        (qd (get quote-decimals ctx))
        (s  (>= bd qd))
        (n  (if s (- bd qd) (- qd bd)))
        (m  (pow u10 n)))
    {
    base : (if s (get base tokens) (* (get base tokens) m)),
    quote: (if s (* (get quote tokens) m) (get quote tokens)),
    }
    ))

(define-read-only
  (lower
   (tokens {base: uint, quote: uint})
   (ctx    {base-decimals: uint, quote-decimals: uint, price: uint}))
  (let ((bd (get base-decimals  ctx))
        (qd (get quote-decimals ctx))
        (s  (>= bd qd))
        (n  (if s (- bd qd) (- qd bd)))
        (m  (pow u10 n)))
    {
    base : (if s (get base tokens) (/ (get base tokens) m)),
    quote: (if s (/ (get quote tokens) m) (get quote tokens)),
    }
    ))

(define-read-only
  (one
   (ctx {base-decimals: uint, quote-decimals: uint, price: uint}))
  (let ((bd (get base-decimals  ctx))
        (qd (get quote-decimals ctx))
        (s  (>= bd qd)))
    (if s (pow u10 bd) (pow u10 qd)) ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; amount = price/one * volume
(define-read-only
  (to-amount
   (price  uint) ;;quote
   (volume uint) ;;base
   (one1   uint))
  (/ (* price volume) one1))

(define-read-only
  (from-amount
   (amount uint) ;;quote
   (price  uint) ;;quote
   (one1   uint))
  (/ (* amount one1) price))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; base  = quote-to-base(base-to-quote(base))
;; quote = base-to-quote(quote-to-base(quote))
(define-read-only
  (base-to-quote
   (tokens uint) ;;volume
   (ctx    {price: uint, base-decimals: uint, quote-decimals: uint}))
  (let ((lifted  (lift {base: tokens, quote: (get price ctx)} ctx))
        (amt0    (to-amount (get quote lifted) (get base lifted) (one ctx)))
        (lowered (lower {base: u0, quote: amt0} ctx)))
    (get quote lowered)))

(define-read-only
  (quote-to-base
   (tokens uint) ;;amount
   (ctx    {price: uint, base-decimals: uint, quote-decimals: uint}))
  (let ((l1      (lift {base: u0, quote: tokens         } ctx))
        (l2      (lift {base: u0, quote: (get price ctx)} ctx))
        (vol0    (from-amount (get quote l1) (get quote l2) (one ctx)))
        (lowered (lower {base: vol0, quote: u0} ctx)))
    (get base lowered) ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API: value of a bag of tokens expressed in terms of each token
(define-read-only
  (value
   (tokens {base: uint, quote: uint})
   (ctx    {price: uint, base-decimals: uint, quote-decimals: uint}))
  (let ((base                  (get base  tokens))
        (quote                 (get quote tokens))
        (base-as-quote         (base-to-quote base  ctx))
        (quote-as-base         (quote-to-base quote ctx))
        (total-as-base         (+ base  quote-as-base))
        (total-as-quote        (+ quote base-as-quote))
        (have-more-base        (> base-as-quote quote))
        (base-excess-as-base   (if have-more-base (- base quote-as-base)  u0))
        (base-excess-as-quote  (if have-more-base (- base-as-quote quote) u0))
        (quote-excess-as-base  (if have-more-base u0 (- quote-as-base base)))
        (quote-excess-as-quote (if have-more-base u0 (- quote base-as-quote)))
        )
    {
    base                  : base,
    quote                 : quote,
    base-as-quote         : base-as-quote,
    quote-as-base         : quote-as-base,
    total-as-base         : total-as-base,
    total-as-quote        : total-as-quote,
    have-more-base        : have-more-base,
    base-excess-as-base   : base-excess-as-base,
    base-excess-as-quote  : base-excess-as-quote,
    quote-excess-as-base  : quote-excess-as-base,
    quote-excess-as-quote : quote-excess-as-quote,
    } ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API: balanced burning
;;
;; first pay out from extra
;; then equally from base/quote
(define-read-only
  (balanced
   (state
    {
    base                  : uint,
    quote                 : uint,
    base-as-quote         : uint,
    quote-as-base         : uint,
    total-as-base         : uint,
    total-as-quote        : uint,
    have-more-base        : bool,
    base-excess-as-base   : uint,
    base-excess-as-quote  : uint,
    quote-excess-as-base  : uint,
    quote-excess-as-quote : uint,
    })
   (burn-value uint) ;;as quote
   (ctx    {price: uint, base-decimals: uint, quote-decimals: uint})
   )
  ;; TODO: this needs to check if we can pay out what we want to pay out
  ;; (or proof that burn-value > unlocked-value implies that)
  ;; and possibly fallback strategies
  (if (get have-more-base state)
      (if (>= (get base-excess-as-quote state) burn-value)
          {base : (quote-to-base burn-value ctx), ;;assert < base-excess-as-base
           quote: u0}
          (let ((base1 (get base-excess-as-base state))
                (left  (- burn-value (get base-excess-as-quote state)))
                (quote (/ left u2))
                (base2 (quote-to-base quote ctx))
                (base  (+ base1 base2)))
            {base : base,
             quote: quote}))
      (if (>= (get quote-excess-as-quote state) burn-value)
          {base : u0,
           quote: burn-value}
           (let ((quote1 (get quote-excess-as-quote state))
                 (left   (- burn-value quote1))
                 (quote2 (/ left u2))
                 (base   (quote-to-base quote2 ctx))
                 (quote  (+ quote1 quote2)))
             {base : base,
              quote: quote})) ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API: borrowing/funding fees
;;
;; Fees are represented as a numerator with a fixed denominator.
;; Intuitively, a reasonable denominator for hourly fees would be something
;; like 100_000 (smallest representable fee = 0.001% per hour).
;; Assuming worst-case block times of ~3 seconds, we need to divide that by ~1000
;; (but we add an extra 0 just in case).
(define-constant DENOM u1000000000) ;;10^9

;; - params.clar calculates a per block fee rate (numerator)
;; - we add decimals and store/use that representations in fees.clar
;; - when calculating absolute fee amounts we need to take the various
;;   representations into account
;;
;; - `x' is a number representing some amount of tokens
;;   on the order of 10^16 (<=10bn with 6 decimals).
;; - 0 <= NUM <= DENOM (10^9)
;; - stacks can represent uints up to ~10^38
;; - x*NUM < 10^25
(define-read-only
  (apply
   (x   uint)
   (num uint)) ;;per-block fee rate
  (let ((fee       (/ (* x num) DENOM))
        (remaining (if (<= fee x) (- x fee) u0))
        (fee_      (if (<= fee x) fee x))
        )
    {
    fee      : fee_,
    remaining: remaining,
    }))

;; FIXME call this in params/static-fees

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API: formula dsl
(define-constant ADD u1)
(define-constant SUB u2)
(define-constant MUL u3)
(define-constant DIV u4)

(define-read-only
 (eval
  (n      uint)
  (instrs (list 100 {op: uint, arg: uint})))
 (fold eval-1 instrs n))

(define-read-only
  (eval-1
   (instr {op: uint, arg: uint})
   (n     uint))
  (let ((op  (get op  instr))
        (arg (get arg instr))
        (res (if (is-eq op ADD)
            (some (+ n arg))
            (if (is-eq op SUB)
                (some (- n arg))
                (if (is-eq op MUL)
                    (some (* n arg))
                    (if (is-eq op DIV)
                        (some (/ n arg))
                        none)
                        ))))
        )
    (unwrap-panic res)))

;;; eof

Functions (7)

FunctionAccessArgs
liftread-onlytokens: {base: uint, quote: uint}, ctx: {base-decimals: uint, quote-decimals: uint, price: uint}
lowerread-onlytokens: {base: uint, quote: uint}, ctx: {base-decimals: uint, quote-decimals: uint, price: uint}
oneread-onlyctx: {base-decimals: uint, quote-decimals: uint, price: uint}
valueread-onlytokens: {base: uint, quote: uint}, ctx: {price: uint, base-decimals: uint, quote-decimals: uint}
applyread-onlyx: uint, num: uint
evalread-onlyn: uint, instrs: (list 100 {op: uint, arg: uint}
eval-1read-onlyinstr: {op: uint, arg: uint}, n: uint