All Downloads are FREE. Search and download functionalities are using the official Maven repository.

convex.torus.exchange.cvx Maven / Gradle / Ivy

The newest version!
'torus.exchange

(call *registry*
      (register {:description ["Torus is a decentralised exchange (DEX) that is fully on-chain and native to Convex."
                               "It establishes automated market makers for fungible assets (see `convex.fungible`)."
                               "Each market offers CVX/Token trading pairs with a liquidity pool."]
                 :name        "Torus DEX"}))

;;;;;;;;;; Imports


(import convex.asset    :as asset)
(import convex.fungible :as fungible)
(import convex.trust    :as trust)


;;;;;;;;;; Values


(def markets
  ;; Map of `token id` -> `market actor address`.
  {})


;;;;;;;;;; API - Creating markets


(defn build-market

  ^{:doc {:description "Creates deployable code for a new Torus token market."
          :examples    [{:code "(deploy (build-market {:token token-address}))"}]
          :signature   [{:params [token torus-addr]}]}}

  ;; Deployable code is  `[fungible-token-code market-code]`.

  [token torus]

  [(fungible/build-token {:supply 0})
   `(do
      (import convex.asset    :as asset)
      (import convex.core     :as core)
      (import convex.fungible :as fungible)

      (def token ~token)
      (def torus ~torus)
      (def token-balance 0)
      
      (defn -qc
    		  ^{:doc {:description "Quantity check."}
    		    :private? true}
    		  [q]
    		  (cond (int? q) q              ;; base case, quantity should always be an integer
    				(nil? q) 0 
    		        (fail :ARGUMENT "Invalid token quantity")))

      (defn add-liquidity
    		  ^{:callable true}
        [amount]
        (let [;; Amount of tokens deposited.
              amount (-qc amount)
     
              ;; Price of token in CVX (double), nil if no current liquidity.
              price (price)
              initial-cvx-balance *balance*
     
              ;; Amount of CVX required (all if initial deposit). Note Long range required
              cvx (core/accept (if price 
                                        (long (* price amount))
                                        *offer*))
              ;; Ensures tokens are transferred from caller to market actor.
              _ (asset/accept *caller* token amount)
              
              ;; Compute new total balances for actor
              new-token-balance   (+ token-balance amount)
              
              ;; Compute number of new shares for depositor = increase in liquidity (%) * current total shares.
              ;; If no current liquidity just initialise with the geometric mean of amounts deposited.
              delta (if (> supply 0)
                      (let [;; Initial size of liquidity pool (geometric mean).
                            liquidity  (sqrt (* (double initial-cvx-balance) token-balance))
                            new-liquidity (sqrt (* (double *balance*) new-token-balance))]
                        (int (* (- new-liquidity liquidity) (/ supply liquidity))))
                      (int (sqrt (* (double amount) cvx))))]
            		   
        ;; Perform updates to reflect new holdings of liquidity pool shares and total token balance (all longs)
        (set-holding *caller* (+ delta (or (get-holding *caller*) 0)))
        (def supply (+ supply delta)) ;; Note Supply set up by fungible/build-token
        (def token-balance new-token-balance)
        delta))


       (defn buy-cvx

         ^{:callable true}

         [amount]

         (let [amount          (int amount)
               _ (cond (< amount 0) (fail :ARGUMENT "Cannot buy negative coin quantity"))
               required-tokens (or (buy-cvx-quote amount)
                                   (fail :LIQUIDITY "Pool cannot supply this amount of CVX"))]
           (asset/accept *caller*
                         [token
                          required-tokens])
           (def token-balance
                (+ token-balance
                   required-tokens))
           ;; Must be done last.
           ;;
           (core/transfer *caller*
                          amount)
           required-tokens))


      (defn buy-cvx-quote
        ^{:callable true}
        [amount]
        ;; Security: check pool can provide.
        (cond 
          (< amount 0) (return nil)
          (>= amount *balance*) (return nil))
        
        (let [;; Computes pool and fees/
              cvx-balance *balance*
              pool        (* (double token-balance) cvx-balance)
              rate        (calc-rate)]
          ;; Computes required payment in tokens.
          (int (ceil (* (+ 1.0
                            rate)
                         (- (/ pool
                               (- cvx-balance
                                  amount))
                            token-balance))))))

       (defn buy-tokens

         ^{:callable true}

         [amount]

         (let [amount       (int amount)
               _ (cond (< amount 0) (fail :ARGUMENT "Cannot buy negative token quantity"))
               required-cvx (or (buy-tokens-quote amount)
                                (fail :LIQUIDITY "Pool cannot supply this amount of tokens"))]
           (core/accept required-cvx)
           (def token-balance
                (- token-balance
                   amount))
           ;; Must be done last.
           ;;
           (fungible/transfer token
                              *caller*
                              amount)
           required-cvx))


      (defn buy-tokens-quote

        ^{:callable true}

        [amount]

        ;; Security: check pool can provide.
        (cond 
          (< amount 0) (return nil)
          (>= amount token-balance) (return nil))
        
        (let [;; Computes pool and fees.
              cvx-balance *balance*
              pool        (* (double token-balance)
                             cvx-balance)
              rate        (calc-rate)]
          ;; Computes required payment in CVX.
          (int (ceil (* (+ 1.0
                            rate)
                         (- (/ pool
                               (- token-balance
                                  amount))
                            cvx-balance))))))


      (defn calc-rate
        ;; TODO. Have variable rate set by torus and/or trade velocity.
        ;; Maybe BASE_FEE / 1 + (THROUGHPUT / LIQUIDITY) ?
        []
        0.001)


      (defn price
        ^{:callable true}
        ;; Price is CVX amount per token, or nil if there are no tokens in liquidity pool.
        []

        (when (> token-balance
                 0)
          (/ (double *balance*)
             token-balance)))


      (defn sell-cvx

        ^{:callable true}

        [amount]

        (let [amount        (int amount)
              gained-tokens (or (sell-cvx-quote amount)
                                (fail :ARGUMENT "Cannot sell negative coin amount"))]
          (core/accept amount)
          (def token-balance
               (- token-balance
                  gained-tokens))
          ;; Must be done last.
          ;;
          (asset/transfer *caller*
                          [token
                           gained-tokens])
          gained-tokens))


      (defn sell-cvx-quote

        ^{:callable true}

        [amount]

        ;; Security: check amount is positive.
        ;;
        (cond (< amount 0) (return nil))
        
        (let [;; Computes pool and fees.
              cvx-balance     *balance*
              pool            (* (double token-balance)
                                 cvx-balance)
              rate            (calc-rate)
              new-cvx-balance (+ cvx-balance
                                 amount)]
          ;; Computes gained Tokens coins from sale.
          (int (/ (- token-balance
                      (/ pool
                         new-cvx-balance))
                    (+ 1.0
                       rate)))))


       (defn sell-tokens

          ^{:callable true}

          [amount]

          (let [amount     (int amount)
                gained-cvx (or (sell-tokens-quote amount)
                               (fail :ARGUMENT "Cannot sell this negative token amount"))]
            (asset/accept *caller*
                          [token
                           amount])
            (def token-balance
                 (+ token-balance
                    amount))
            ;; Must be done last.
            ;;
            (core/transfer *caller*
                           gained-cvx)
            gained-cvx))


       (defn sell-tokens-quote

         ^{:callable true}

         [amount]

         ;; Security: check amount is positive.
         (cond (< amount 0) (return nil))
         
         (let [;; Computes pool and fees.
               cvx-balance       *balance*
               pool              (* (double token-balance)
                                    cvx-balance)
               rate              (calc-rate)
               new-token-balance (+ token-balance
                                    amount)]
           ;; Computes gained Convex coins from sale.
           (int (/ (- cvx-balance
                       (/ pool
                          new-token-balance))
                    (+ 1.0
                       rate)))))


      (defn withdraw-liquidity
        ^{:callable true}
        [shares]
        (let [;; Amount of shares to withdraw.
              shares       (-qc shares)
              ;; Shares of holder.
              own-holding  (or (get-holding *caller*) 0)
              _            (assert (<= 0
                                       shares
                                       own-holding))
              proportion   (if (> supply
                                  0)
                             (/ (double shares)
                                supply)
                             0.0)
              coin-refund  (int (* proportion
                                    *balance*))
              token-refund (int (* proportion
                                    token-balance))]
             ;; SECURITY:
             ;; 1. Update balances then transfer coins first. Risk of re-entrancy attack if transfers are made while
             ;;    this actor is in an inconsistent state so we MUST do accounting first.
             (def token-balance
                  (- token-balance
                     token-refund))
             (set-holding *caller*
                          (- own-holding
                             shares))
             (def supply
                  (- supply
                     shares))
              ;; 2. Transfer back coins. Be aware caller might do *anything* in transfer callbacks!
              (transfer *caller*
                        coin-refund)
              ;; 3. Finally transfer asset. We've accounted this already, so safe 
              ;; TODO. Decide which of these is best
              ;;(asset/transfer *caller* [token token-refund] :withdraw)
              (fungible/transfer token
                                 *caller*
                                 token-refund)
              shares)))])



(defn create-market

  ^{:callable true
    :doc       {:description "Gets or creates the canonical market for a token."
                :examples    [{:code "(deploy (build-market {:token token-address}))"}]
                :signature   [{:params [config]}]}}

  [token]

  (when-not (= *address*
               ~*address*)
    (return (call ~*address*
                  (create-market token))))
  (assert (callable? token))
  (let [existing-market (get markets
                             token)]
    (or existing-market
        (let [market (deploy (build-market token
                                           *address*))]
          (set! markets
               (assoc markets
                      token
                      market))
          market))))



(defn get-market

  ^{:doc {:description "Gets the canonical market for a token. Returns nil if the market does not exist."
          :examples    [{:code "(deploy-once (build-market {:token token-address}))"}]
          :signature   [{:params [token]}]}}

  [token]

  (get markets
       token))


;;;;;;;;;; API - Handling markets


(defn add-liquidity

  ^{:doc {:description "Adds an amount of a token to the liquidity pool, matched by an amount of CVX. "
          :signature   [{:params [token token-amount cvx-amount]}]}}

  ([token token-amount]
    (if-let [p (price token)]
      (recur token token-amount (inc (int (* p token-amount))))
      (fail "No liquidity to set market price yet")))

  ([token token-amount cvx-amount]
	  (let [market (create-market token)]
	    (asset/offer market token token-amount)
	    (call market
	          (int cvx-amount)
	          (add-liquidity token-amount)))))



(defn buy
  ^{:doc {:description "Buy an amount of a token using a given token (token swap)."
          :signature   [{:params [of-token amount with-token]}]}}
  [of-token amount with-token]

  (let [market     (or (get-market of-token)
                       (fail (str "Torus: market does not exist for token: " of-token)))
        cvx-amount (or (call market (buy-tokens-quote amount))
                       (fail :LIQUIDITY "No liquidity available to buy token"))
        sold       (buy-cvx with-token cvx-amount)]
    (buy-tokens of-token amount)
    sold))



(defn buy-cvx
  ^{:doc {:description "Buy and amount of CVX using a given token."
          :signature   [{:params [token amount]}]}}

  [token amount]

  (let [market (or (get-market token)
                   (fail :LIQUIDITY
                         (str "Torus: market does not exist for token: " token)))]
    ;; Note we can offer all tokens, market will accept what it needs to complete order.
    (asset/offer market
                 [token
                  (fungible/balance token
                                    *address*)])
    (call market (buy-cvx amount))))



(defn buy-tokens
  ^{:doc {:description "Buy a specified amount of a token, using whatever CVX required."
          :signature   [{:params [token amount]}]}}
  [token amount]
  (let [market (or (get-market token)
                   (fail :LIQUIDITY
                         (str "Torus: market does not exist for token: " token)))]
    ;; Note we can offer all available CVX, since we trust the market to only accept the amount required
    (call market *balance* (buy-tokens amount))))


(defn buy-quote
  ^{:doc {:description "Gets a quote to buy an amount of a specifies token, in CVX or oprtional token to swap."
          :signature   [{:params [of-token amount]}
                        {:params [of-token amount with-token]}]}}
  ([of-token amount]
    (when-let [market (get-market of-token)]
      (call market (buy-tokens-quote amount))))

  ([of-token amount with-token]
    (when-let [market (get-market with-token)]
      (when-let [cvx-amount (buy-quote of-token amount)]
        (call market (buy-cvx-quote cvx-amount))))))



(defn price
  ^{:doc {:description "Gets the current price for a token, in CVX or an optional given currency. Returns nil if a market with liquidity does not exist."
          :examples  [{:code "(price USD)"}
                      {:code "(price GBP USD)"}]
          :signature [{:params [token]}
                      {:params [token currency]}]}}
  ([token]
    (if-let [market (get-market token)]
      (call market (price))))

  ([token currency]
   (let [market.token    (or (get-market token)
                             (return nil))
         price.cvx       (or (call market.token (price))
                             (return nil))
         market.currency (or (get-market currency)
                             (return nil))
         price.currency  (or (call market.currency (price))
                             (return nil))]
     (/ price.cvx
        price.currency))))



(defn sell
  ^{:doc {:description "Sells a given amount of a fungible token in exchange for another token"
          :signature   [{:params [of-token amount with-token]}]}}

  [of-token amount with-token]

  (let [cvx-amount (sell-tokens of-token amount)]
    (sell-cvx with-token cvx-amount)))



(defn sell-cvx
  ^{:doc {:description "Sell an amount of CVX for a given token."
          :signature   [{:params [token amount]}]}}
  [token amount]
  (let [market (or (get-market token)
                   (fail :LIQUIDITY (str "Torus: market does not exist for token: " token)))]
    ;; Call with an offer equal to the amount of CVX being sold.
    (call market
          amount
          (sell-cvx amount))))



(defn sell-quote
  ^{:doc {:description "Get a quote for selling an amount of a token, in CVX or optional swap token."
          :signature   [{:params [of-token amount]}
                        {:params [of-token amount with-token]}]}}
  ([of-token amount]
   (when-let [market (get-market of-token)]
    (call market
          (sell-tokens-quote amount))))

  ([of-token amount with-token]
   (when-let [market (get-market with-token)]
     (when-let [cvx-amount (sell-quote of-token
                                       amount)]
       (call market
             (sell-cvx-quote cvx-amount))))))

(defn sell-tokens
  ^{:doc {:description "Sell an amount of tokens at current CVX price"
          :signature   [{:params [token amount]}]}}
  [token amount]
  (let [market (or (get-market token)
                   (fail :LIQUIDITY
                         (str "Torus: market does not exist for token: " token)))]
    ;; Offer the amount of tokens being sold.
    (asset/offer market [token amount])
    (call market (sell-tokens amount))))


(defn withdraw-liquidity
  ^{:doc {:description "Withdraw an amount of liquidity for a token."
          :signature   [{:params [token shares]}]}}
  [token shares]
  (let [market (or (get-market token)
                   (fail :STATE "No market exists to withdraw liquidity"))]
    (call market (withdraw-liquidity shares))))




© 2015 - 2024 Weber Informatics LLC | Privacy Policy