convex.asset.share.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!
'asset.share
(call *registry*
(register {:description ["An actor that supports revenue sharing of an underlying asset"]
:name "Revenue Share actor"}))
;;;;;;;;;; Setup
(import convex.asset :as asset-lib)
(import convex.trust :as trust)
;;;;;;;;;; State
;; Token metadata is stored here
;; Map of { id -> [controller supply underlying-asset underlying-balance underlying-claimed] ]
(def tokens {})
;; Per-user data is stored in holdings: Map of { id -> [balance offers claimed] }
;; where:
;; balance is a non-negative Integer
;; offers is nil or a map of { address -> offer amount }
;; claimed is amount of underlying asset claimed
(def counter 0)
;; The BIG idea: by tracking the number of claimed underlying tokens for each holder
;; as well as the total amount claimed, we can allow holders to claim a proportionate share
;; of the underlying balance
;;
;; Complexities to bear in mind:
;; - when shares are transferred, we also transfer unclaimed amount
;; - multiple share tokens may have same underlying asset, so they must track independent balances
;;;;;;;;; Private functions
(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 -set-balance
[addr id bal]
(let [h (get-holding addr)
orec (get h id) ;; old record
rec (if orec (assoc orec 0 bal) [bal nil])]
(set-holding addr (assoc h id rec))))
(defn -get-balance
[addr id]
(let [h (get-holding addr)
rec (get h id)]
(if rec (nth rec 0) 0)))
(defn -get-token
[id]
(or (get tokens id) (fail :STATE "Share token does not exist")))
;;;;;;;;;; Public API
(defn create
^{:callable true
:doc {:description "Creates a new share token and returns its id. Caller will be controller of token. Returns ID."
:signature [{:params [underlying]}]}}
([underlying]
(let [id (inc counter)]
(def tokens (assoc tokens id [*caller* 0 underlying 0 0]))
(def counter id)
id)))
;;; Trust / control SPI
(defn change-control
^{:callable true}
[controller]
(or (callable? controller) (fail :ARGUMENT "controller must be a callable value"))
(let [trec (-get-token *scope*)
cont (nth trec 0)
_ (or
(trust/trusted? cont *caller* :control)
(fail :TRUST "Not allowed to update controller"))
nrec (assoc trec 0 controller)]
(set! tokens (assoc tokens *scope* nrec))))
;;; Asset SPI
(defn direct-transfer
^{:callable true}
[addr amount data]
(let [addr (address addr)
amount (-qc amount)
id *scope*
bal (-get-balance *caller* id)
tbal (-get-balance addr id)]
;; Amount must be in valid range.
;;
(assert (<= 0
amount
bal))
;; Need this check in case of self-transfers.
(when (= *caller*
addr)
(return amount))
;; TODO: need to transfer unclaimed quantity
(-set-balance *caller* id (- bal amount))
(-set-balance addr id (+ tbal amount))
amount))
(defn balance
^{:callable true}
([addr]
(let [hs (get-holding addr)
rec (get hs *scope*)]
(if rec (nth rec 0) 0))))
(defn decimals
^:callable
[] 0)
(defn total-supply
^:callable
[]
(get-in tokens [*scope* 1]))
(defn claim
^{:callable true}
([amt]
(let [token (-get-token *scope*)
hs (get-holding addr)
rec (or (get hs *scope*) (fail "No holding to claim from"))]
(TODO))))
(defn
receive-asset
^{:callable true}
[token quantity data]
(let [token (-get-token *scope*)
[controller supply utoken ubalance uclaimed] token
quantity (asset-lib/accept *caller* token quantity)
_ (or (int? quantity) (fail "Non-integer quantity"))
nubalance (+ ubalance quantity)
nrec (assoc token 3 nubalance)]
(set! tokens (assoc tokens *scope* nrec))
quantity))
(defn accept
^{:callable true}
[sender quantity]
(let [id *scope*
quantity (-qc quantity)
_ (cond
(zero? quantity) (return 0)
(< quantity 0) (fail "Negative accept amount"))
receiver *caller*
hs (get-holding sender) ;; holdings of sender
rec (get hs id)]
(if rec
(let [os (nth rec 1) ;; offers map
off (or (get os receiver) (fail :STATE "No offer to receiver"))
_ (cond (< off quantity) (fail :STATE "insufficient offer"))
bal (nth rec 0)
nbal (- bal quantity)
_ (cond (< nbal 0) (fail :FUNDS "insufficent balance to accept"))
noff (- off quantity)
nos (cond (<= noff 0) (dissoc os receiver) (assoc os receiver noff))
nrec [nbal nos]]
;; Update offer and balance of sender
(set-holding sender (assoc hs id nrec))
;; Add accepted quantity to receiver
(-set-balance receiver id (+ (-get-balance receiver id) quantity))
quantity)
(fail "No offers from sender"))))
(defn get-offer
^{:callable true}
[sender receiver]
(let [id *scope*
rec (or (get (get-holding sender) id) (return 0))]
(or (get (nth rec 1) receiver) 0)))
(defn offer
^{:callable true}
[receiver quantity]
(let [id *scope*
quantity (-qc quantity)
receiver (address receiver)
hs (get-holding *caller*)
rec (get hs id)]
(if rec
(let [os (nth rec 1) ;; offers map
nrec (if (<= quantity 0)
(assoc rec 1 (dissoc os receiver))
(assoc rec 1 (assoc os receiver quantity)))]
(set-holding *caller* (assoc hs id nrec)))
(do ;; create a new record with given offer
(or (get tokens id) (fail "token does not exist"))
(set-holding *caller* {id [0 {receiver quantity}]})))
quantity ;; return value is quantity offered
))
(defn mint
^{:callable true}
[amount]
(let [token (-get-token *scope*)
[controller supply utoken ubalance uclaimed] token]
(when-not (trust/trusted? controller *caller* :mint)
(fail :TRUST "No rights to mint"))
(let [amount (int amount) ;; Mint amount.
new-supply (+ supply amount)
bal (-get-balance *caller* *scope*)
new-bal (+ bal amount)]
;; New supply must be in valid range.
(assert (<= 0 new-supply))
;; new balance must be in range
(assert (<= 0 new-bal))
;; Update state
(-set-balance *caller* *scope* new-bal)
(let [new-token (assoc token 1 new-supply)]
(def tokens (assoc tokens *scope* new-token))
new-supply))))
(defn quantity-add
^{:callable true}
[a b]
(let [a (if a (int a) 0)
b (if b (int b) 0)]
(+ a b)))
(defn quantity-sub
^{:callable true}
[a b]
(let [a (if a (int a) 0)
b (if b (int b) 0)]
(if (>= a b) (- a b) 0)))
(defn quantity-subset?
^{:callable true}
[a b]
(let [a (if a (int a) 0)
b (if b (int b) 0)]
(<= a b)))