convex.torus.exchange.cvx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
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))))