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

convex.asset.asset.cvx Maven / Gradle / Ivy

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))))




© 2015 - 2024 Weber Informatics LLC | Privacy Policy