convex.asset.asset.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!
'convex.asset
(call *registry*
(register {:description ["An asset is either:"
"- A vector `[asset-path quantity]` indicating an asset managed by an actor"
"- A map `asset-path` -> `quantity` indicating multiple assets"
"Quantities are asset-specific."
"For a fungible currency, quantity is the amount ; for a non-fungible token, quantity may be a set of token ids."
"Key functions from this library are `balance`, `owns?`, and `transfer`."
"Other functions such as `accept` and `offer` provide more fine-grained control when required."
"Implementing a new asset means deploying an actor which defines the following callable functions:"
"- `(accept sender quantity)`"
"- `(balance owner)`"
"- `(check-transfer sender receiver quantity)`"
"- `(direct-transfer receiver quantity)`"
"- `(get-offer sender receiver)`"
"- `(offer receiver quantity)`"
"- `(quantity-add asset-address a b)`"
"- `(quantity-sub asset-address a b)`"
"- `(quantity-subset a b)` ; returns true if `a` is considered a subset of `b`"
"For more information, see those functions defined in this library whose purpose is to delegate to asset implementations in a generic way."]
:name "Convex Asset API library"}))
;;;;;;;;;; Forward declarations
(declare balance transfer)
;;;;;;;;;; Private
;; Used by asset handling functions to treat a vector as a map
(defn -make-map
^:private
[a]
(cond
(map? a) a
(vector? a) (conj {} a)
(nil? a) {}))
;;;;;;;;;; General info
(defn total-supply
^{:doc {:description "Gets the total supply of an asset. i.e. the union of all balances. May return nil if not supported."
:examples [{:code "(total-supply my-token)"}]
:signature [{:params [token amount]}]}}
[token]
(query
(try
(call token
(total-supply))
nil)))
;;;;;;;;;; Transfers
(defn accept
^{:doc {:description ["Accepts asset from sender."
"If asset contains multiple assets, accepts each in turn. MUST fail if the asset cannot be accepted."]
:examples [{:code "(accept sender [fungible-token-address 1000])"}]
:signature [{:params [sender asset]}
{:params [sender asset-path quantity]}]
:errors {:STATE "If there is no sufficient offer to accept."
:FUNDS "If sender has insufficient balance."}}}
([sender asset]
(cond
(vector? asset)
(let [path (first asset)
quantity (second asset)]
(recur sender path quantity))
(map? asset)
(reduce (fn [m [path quantity]]
(assoc m
path
(accept sender path quantity)))
{}
asset)
;; a nil quantity can always be accepted
(nil? asset) nil
(fail "Bad asset argument")))
([sender path quantity]
(call path (accept sender quantity))))
(defn check-transfer
;; Independently of general transfer, you can test whether there are restrictions on transferring
^{:doc {:description ["Checks whether sender can transfer this asset to receiver."
"Returns a descriptive failure message if there is a restriction prohibiting transfer, or nil if there is no restriction."]
:examples [{code "(check-transfer sender receiver [fungible-token-address 1000])"}
{:code "(check-transfer sender receiver [non-fungible-token-address #{1 4 6}])"}]
:signature [{:params [sender receiver [asset-address quantity]]}]}}
[sender receiver [path quantity]]
(or (callable? path 'check-transfer) (return nil))
(query (call path
(check-transfer sender receiver quantity))))
(defn get-offer
^{:doc {:description ["Gets the current offer from `sender` to `receiver` for a given asset."
"Returns the quantity representing the current offer. Will be the 'zero' quantity if no open offer exists."]
:examples [{:code "(get-offer asset-path sender receiver)"}]
:signature [{:params [asset-path sender receiver]}]}}
[path sender receiver]
(query (call path
(get-offer sender
receiver))))
;; Smart contracts may need to offer and accept assets to avoid unconditional transfers.
(defn offer
^{:doc {:description ["Opens an offer of an `asset` to a `receiver`, which makes it possible for the receiver to 'accept' up to this quantity."
"May result in an error if the asset does not support open offers."]
:examples [{:code "(offer receiver [fungible-token-address 1000])"}
{:code "(offer receiver non-fungible-token-address #{1 4 6})"}]
:signature [{:params [receiver asset]}
{:params [receiver asset-path quantity]}]}}
([receiver asset]
(cond
(vector? asset)
(let [[path quantity] asset]
(recur receiver path quantity))
(map? asset)
(reduce (fn [m [path quantity]]
(assoc m path (offer receiver path quantity)))
{}
asset)))
([receiver path quantity]
(call path (offer receiver quantity))))
(defn transfer
^{:doc {:description "Transfers asset to receiver. `data` is an arbitrary value, which will be passed to the receiver's `receive-asset` method or directly trasnferred is the target is a user account."
:examples [{:code "(transfer receiver [fungible-token-address 1000])"}
{:code "(transfer receiver [non-fungible-token-address #{1 4 6}] optional-data)"}]
:signature [{:params [receiver asset]}
{:params [receiver asset data]}]}}
([receiver asset]
(cond
(vector? asset)
(let [[path quantity] asset]
(recur receiver path quantity nil))
(map? asset)
(reduce (fn [m [path quantity]]
(assoc m
path
(transfer receiver path quantity nil)))
{}
asset)
(nil? asset) nil
:else
(fail :ARGUMENT "Invalid asset")))
([receiver path quantity]
(recur receiver path quantity nil))
([receiver path quantity data]
(cond
;; First check if receiver has a callable receive-asset function. If so, use this
(callable? receiver 'receive-asset)
(do
(offer receiver path quantity) ;; Offer correct quantity
(call receiver
(receive-asset path quantity data)))
;; An actor without a receive-asset function is not a valid receiver?
(actor? receiver) (fail :STATE "Target Actor does not have receive-asset function")
(call path (direct-transfer receiver quantity data)))))
;;;;;;;;;; Asset creation
(defn create
^{:doc {:description ["Creates a scoped sub-asset given an actor address."
"Return value will be a scoped asset path to the new sub-asset."]
:examples [{:code "(creater factory-address)"}]
:signature [{:params [addr]}]}}
([addr]
(let [scope (call addr (create))]
[addr scope]))
([addr options]
(let [scope (call addr (create options))]
[addr scope])))
;;;;;;;;;; Ownership
(defn balance
^{:doc {:description ["Returns asset balance for a specified owner, or for the current address if not supplied."
"Return value will be in the quantity format as specified by the asset type."]
:examples [{:code "(balance asset-address)"}
{:code "(balance [asset-address asset-id] owner)"}]
:signature [{:params [asset-path]}
{:params [asset-path owner]}]}}
([path]
(recur path *address*))
([path owner]
(query ;; external call, so use query to avoid re-entrant secuity risks
(call path (balance owner)))))
(defn burn
^{:doc {:description "Burns a quantity of the given asset, if allowed by the implementation. Amount must be a valid quantity owned by the user."
:examples [{:code "(burn [my-token-address :FOO] 1000)"}]
:signature [{:params [path amount]}]}}
([path amount]
(call path
(burn amount))))
(defn mint
^{:doc {:description "Mints a quantity of the given asset. User must have minting privileges."
:examples [{:code "(mint [my-token-address :FOO] 1000)"}]
:signature [{:params [token amount]}]}}
[path amount]
(call path
(mint amount)))
(defn owns?
^{:doc {:description "Tests whether owner owns at least a given quantity of an asset",
:examples [{:code "(owns? owner [fungible-token-address 1000])"}
{:code "(owns? owner [non-fungible-token-address #{1 4 6}])"}]
:signature [{:params [owner asset]}]}}
([owner asset]
(cond
(vector? asset)
(let [[path quantity] asset]
(recur owner path quantity)))
(map? asset)
(reduce (fn [result [asset-path quantity]]
(if (owns? owner asset-path quantity)
true
(reduced false)))
true
asset)
;; Interpret nil as the 'zero' asset, which everybody owns
(nil? asset)
true)
([owner path quantity]
(query ;; query because this is an external call
(let [bal (balance path owner)]
(call path
(quantity-subset? quantity bal))))))
;;;;;;;;;; Quantities
(defn quantity-add
^{:doc {:description ["Adds two asset quantities. Quantities must be specified in the format required by the asset type."
"Nil may be used to indicate the 'zero' quantity."]
:examples [{:code "(quantity-add fungible-token 100 1000)"}
{:code "(quantity-add non-fungible-token #{1 2} #{3 4})"}
{:code "(quantity-add [token-a 100] [token-b 1000])"}]
:signature [{:params [asset-a asset-b]}
{:params [asset-address a b]}]}}
([asset-a asset-b]
(let [asset-a (-make-map asset-a)
asset-b (-make-map asset-b)]
(reduce (fn [m [path qb]]
(let [qa (get m
path)]
(assoc m
path
(quantity-add path
qa
qb))))
asset-a
asset-b)))
([path a b]
(query (call path
(quantity-add a b)))))
(defn quantity-sub
^{:doc {:description ["Subtracts an asset quantity from another quantity. Quantities must be specified in the format required by the asset type."
"Subtracting a larger amount from a smaller amount should return 'zero' or equivalent, although the exact meaning of this operation may be asset-specific."
"`nil` can be used yo indicate the 'zero' quantity in inputs."]
:examples [{:code "(quantity-sub fungible-token 500 300)"}
{:code "(quantity-sub non-fungible-token #{1 2 3 4} #{2 3})"}]
:signature [{:params [asset-a asset-b]}
{:params [asset-address a b]}]}}
([asset-a asset-b]
(let [asset-a (-make-map asset-a)
asset-b (-make-map asset-b)]
(reduce (fn [m [asset-address qb]]
(let [qa (get m
asset-address)]
(if (= qa
qb)
(dissoc m
asset-address)
(assoc m
asset-address
(quantity-sub asset-address
qa
qb)))))
asset-a
asset-b)))
([path a b]
(call path (quantity-sub a b))))
(defn quantity-zero
^{:doc {:description "Returns the unique 'zero' quantity for the given asset."
:examples [{:code "(quantity-zero fungible-token)"
:result 0}
{:code "(quantity-zero non-fungible-token)"
:result #{}}]
:signature [{:params [asset-address]}]}}
[path]
(call path (quantity-add nil nil)))
(defn quantity-contains?
^{:doc {:description "Returns true if first quantity is >= second quantity. Any valid quantity must contain the 'zero' quantity."
:examples [{:code "(quantity-contains? fungible-token 100 60)"
:result true}
{:code "(quantity-contains? non-fungible-token #{1 2} #{2 3})"
:result false}]
:signature [{:params [asset-a asset-b]}
{:params [asset a b]}]}}
([asset-a asset-b]
(let [asset-a (-make-map asset-a)
asset-b (-make-map asset-b)]
(reduce (fn [m [path qb]]
(let [qa (get asset-a
path)]
(cond
(= qa qb)
true
(quantity-contains? path qa qb)
true
:else
(reduced false))))
true
asset-b)))
([path a b]
(call path (quantity-subset? b a))))