From ed7f4ef6680a86641786510592f223cf5d333493 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Thu, 10 Sep 2020 12:47:41 -0400 Subject: [PATCH] started to refactor code. --- src/clj/skirmish/ledger/core.clj | 96 ++----------- src/clj/skirmish/ledger/data_layer.clj | 88 ++++++++++++ src/clj/skirmish/ledger/routes.clj | 183 +++++++++++++++++++++++++ test/clj/skirmish/ledger/core_test.clj | 6 - test/clj/skirmish/ledger/test_core.clj | 38 ++--- 5 files changed, 300 insertions(+), 111 deletions(-) create mode 100644 src/clj/skirmish/ledger/data_layer.clj create mode 100644 src/clj/skirmish/ledger/routes.clj delete mode 100644 test/clj/skirmish/ledger/core_test.clj diff --git a/src/clj/skirmish/ledger/core.clj b/src/clj/skirmish/ledger/core.clj index e7b20a3..967aa1a 100644 --- a/src/clj/skirmish/ledger/core.clj +++ b/src/clj/skirmish/ledger/core.clj @@ -7,6 +7,7 @@ [compojure.route :refer [not-found]] [skirmish.exchange-gateway.exchange-gateway :refer [create-routes-for-exchange]] [skirmish.rest.util :refer [build-response get-body cors-middleware]] + [skirmish.ledger.data-layer :as data-layer] [skirmish.ledger.accounts] [datomic.api :as d] [taoensso.timbre :as timbre] @@ -17,86 +18,6 @@ ;; the ledger is the central - - -(defn compute-simple-face-value [amount daily-interest-rate deposit-date value-date] - (let [min-in-milliseconds (infix 60 * 1000) - minute-interest-rate (infix daily-interest-rate / (24 * 60. * 10000.)) - ite (quot (- (. value-date getTime) (. deposit-date getTime)) min-in-milliseconds) - base (infix 1 + minute-interest-rate)] - (infix amount * base ** ite))) - -(defn- in-interval? [[_ start end] x] - (let [x-time (. x getTime)] - (and (<= (. start getTime) x-time) - (< x-time (. end getTime))))) - -(defn- larger-then-interval? [[ _ start end] x] - (and (<= (. x getTime) (. end getTime)) - (<= (. x getTime) (. start getTime)))) - - -(defn- split-interest-rate-intervals [interest-rates-intervals deposit-date value-date] - (let [xf (comp (drop-while #((comp not in-interval?) % deposit-date)) - (take-while #((comp not larger-then-interval?) % value-date))) - ret (into [] xf interest-rates-intervals) - ret (assoc-in ret [0 1] deposit-date) - ret (assoc-in ret [(-> ret count dec) 2] value-date)] - ret)) - -(defn- find-interest-rate-for [interest-rates-intervals d] - (if (in-interval? (first interest-rates-intervals) d) - (ffirst interest-rates-intervals) - (recur (rest interest-rates-intervals) d))) - - -(defn split-interest-rates [interest-rates deposit-date value-date] - (let [interest-rates (into [[0 (new java.util.Date 0)]] interest-rates) - interest-rates (conj interest-rates [0 (new java.util.Date Long/MAX_VALUE)]) - interest-rates-intervals (partition 2 1 interest-rates) - interest-rates-intervals (map (juxt ffirst (comp second first) (comp second second)) interest-rates-intervals)] - (if (= deposit-date value-date) - [[(find-interest-rate-for interest-rates-intervals deposit-date) deposit-date value-date]] - (split-interest-rate-intervals interest-rates-intervals deposit-date value-date)))) - -(defn compute-face-value [amount interest-rates deposit-date value-date] - (loop [interest-rates (split-interest-rates interest-rates deposit-date value-date) - amount amount] - (let [[interest-rate start end] (first interest-rates) - amount (compute-simple-face-value amount interest-rate start end)] - (if (empty? (rest interest-rates)) - (long amount) - (recur (rest interest-rates) amount))))) - -(defn get-interest-rate-history [db sec-id] - (let [history (d/history db) - interest-rates (d/q '[:find ?interest-rate ?v - :in $ ?eid - :where [?e :skirmish.security/interest-rate ?interest-rate ?tx] - [?e :skirmish.security/sec-id ?sec-id] - [?tx :db/txInstant ?v]] - history sec-id)] - (sort-by second interest-rates))) - -(defn compute-face-value-from-db [db amount sec-id date] - (compute-face-value amount - (get-interest-rate-history db sec-id) - date - (new java.util.Date))) - -(defn get-amount-in-account [db account-id] - (let [[amount sec-id pk date] (d/q '[:find [?amount ?sec-id ?pk ?v] - :in $ ?account-id - :where [?e :skirmish.ledger/owner-public-key ?account-id] - [?e :skirmish.ledger/amount ?amount ?tx] - [?e :skirmish.ledger/sec ?sec] - [?sec :skirmish.security/sec-id ?sec-id] - [?e :skirmish.ledger/owner-public-key ?pk] - [?tx :db/txInstant ?v]] db account-id)] - {:amount (when (some? sec-id) (compute-face-value-from-db db amount sec-id date)) - :sec-id sec-id - :account-public-key pk})) - ;; an :account-id is the public key of a ;; an :account-secret is an :account-id private key @@ -114,7 +35,7 @@ "Returns the value of an account." (fn [{{account-id :account-id} :route-params :as request}] (let [db (d/db conn) - account-info (get-amount-in-account db account-id)] + account-info (data-layer/get-amount-in-account db account-id)] (get-account-body request account-info)))) (s/def ::transaction (s/cat :type #{:transfer-immediately} @@ -152,7 +73,7 @@ name :skirmish.security/name number-of-decimal :skirmish.security/number-of-decimal type_ :skirmish.security/type} e - interest-rate-history (get-interest-rate-history db id) + interest-rate-history (data-layer/get-interest-rate-history db id) volumes (d/q '[:find ?amount ?date :in $ ?id :where [?e :skirmish.ledger/sec ?id] @@ -160,7 +81,7 @@ [?tx :db/txInstant ?date]] db id) now (new java.util.Date) - volume (reduce + 0 (map #(compute-face-value (first %) interest-rate-history (second %) now) volumes))] + volume (reduce + 0 (map #(data-layer/compute-face-value (first %) interest-rate-history (second %) now) volumes))] {:sec-id sec-id :interest-rate interest-rate :volume (or volume 0) @@ -184,7 +105,7 @@ (fn [{body :body :as request}] (try+ (let [[sec-id interest-rate] (-> body get-body get-new-security) - new-sec @(d/transact conn [[:skirmish.ledger/create-security sec-id interest-rate]])] + new-sec @(data-layer/create-security conn sec-id interest-rate)] (build-response request {:body {:success true} :headers {"location" (str "/api/1" "/ledger/securities/" (name sec-id))}})) (catch [:error-type :4XX] e (build-response request e)) @@ -219,7 +140,8 @@ (defn get-available-securities [conn] (fn [request] - (let [securities (d/q '[:find ?s :where [?e :skirmish.security/sec-id ?s]] (d/db conn))] + (let [db (d/db conn) + securities (data-layer/get-all-securities-id db)] (build-response request (get-available-securities-body securities))))) @@ -259,8 +181,8 @@ (defn get-available-accounts [conn] (fn [request] - (let [securities (d/q '[:find ?pk :where [?e :skirmish.ledger/owner-public-key ?pk]] (d/db conn))] - (build-response request (get-available-accounts-body securities))))) + (let [db (d/db conn)] + (build-response request (get-available-accounts-body (data-layer/get-all-account-public-key db)))))) (defn get-api-description [request] diff --git a/src/clj/skirmish/ledger/data_layer.clj b/src/clj/skirmish/ledger/data_layer.clj new file mode 100644 index 0000000..7bd542f --- /dev/null +++ b/src/clj/skirmish/ledger/data_layer.clj @@ -0,0 +1,88 @@ +(ns skirmish.ledger.data-layer + (:require [datomic.api :as d] + [infix.macros :refer [infix]])) + +(defn get-all-account-public-key [db] + (d/q '[:find ?pk :where [?e :skirmish.ledger/owner-public-key ?pk]] db)) + +(defn compute-simple-face-value [amount daily-interest-rate deposit-date value-date] + (let [min-in-milliseconds (infix 60 * 1000) + minute-interest-rate (infix daily-interest-rate / (24 * 60. * 10000.)) + ite (quot (- (. value-date getTime) (. deposit-date getTime)) min-in-milliseconds) + base (infix 1 + minute-interest-rate)] + (infix amount * base ** ite))) + +(defn- in-interval? [[_ start end] x] + (let [x-time (. x getTime)] + (and (<= (. start getTime) x-time) + (< x-time (. end getTime))))) + +(defn- larger-then-interval? [[ _ start end] x] + (and (<= (. x getTime) (. end getTime)) + (<= (. x getTime) (. start getTime)))) + +(defn- split-interest-rate-intervals [interest-rates-intervals deposit-date value-date] + (let [xf (comp (drop-while #((comp not in-interval?) % deposit-date)) + (take-while #((comp not larger-then-interval?) % value-date))) + ret (into [] xf interest-rates-intervals) + ret (assoc-in ret [0 1] deposit-date) + ret (assoc-in ret [(-> ret count dec) 2] value-date)] + ret)) + +(defn- find-interest-rate-for [interest-rates-intervals d] + (if (in-interval? (first interest-rates-intervals) d) + (ffirst interest-rates-intervals) + (recur (rest interest-rates-intervals) d))) + +(defn split-interest-rates [interest-rates deposit-date value-date] + (let [interest-rates (into [[0 (new java.util.Date 0)]] interest-rates) + interest-rates (conj interest-rates [0 (new java.util.Date Long/MAX_VALUE)]) + interest-rates-intervals (partition 2 1 interest-rates) + interest-rates-intervals (map (juxt ffirst (comp second first) (comp second second)) interest-rates-intervals)] + (if (= deposit-date value-date) + [[(find-interest-rate-for interest-rates-intervals deposit-date) deposit-date value-date]] + (split-interest-rate-intervals interest-rates-intervals deposit-date value-date)))) + +(defn compute-face-value [amount interest-rates deposit-date value-date] + (loop [interest-rates (split-interest-rates interest-rates deposit-date value-date) + amount amount] + (let [[interest-rate start end] (first interest-rates) + amount (compute-simple-face-value amount interest-rate start end)] + (if (empty? (rest interest-rates)) + (long amount) + (recur (rest interest-rates) amount))))) + +(defn get-interest-rate-history [db sec-id] + (let [history (d/history db) + interest-rates (d/q '[:find ?interest-rate ?v + :in $ ?eid + :where [?e :skirmish.security/interest-rate ?interest-rate ?tx] + [?e :skirmish.security/sec-id ?sec-id] + [?tx :db/txInstant ?v]] + history sec-id)] + (sort-by second interest-rates))) + +(defn compute-face-value-from-db [db amount sec-id date] + (compute-face-value amount + (get-interest-rate-history db sec-id) + date + (new java.util.Date))) + +(defn get-amount-in-account [db account-id] + (let [[amount sec-id pk date] (d/q '[:find [?amount ?sec-id ?pk ?v] + :in $ ?account-id + :where [?e :skirmish.ledger/owner-public-key ?account-id] + [?e :skirmish.ledger/amount ?amount ?tx] + [?e :skirmish.ledger/sec ?sec] + [?sec :skirmish.security/sec-id ?sec-id] + [?e :skirmish.ledger/owner-public-key ?pk] + [?tx :db/txInstant ?v]] db account-id)] + {:amount (when (some? sec-id) (compute-face-value-from-db db amount sec-id date)) + :sec-id sec-id + :account-public-key pk})) + +(defn get-all-securities-id [db] + (d/q '[:find ?s :where [?e :skirmish.security/sec-id ?s]] db)) + +(defn create-security [conn sec-id interest-rate] + (d/transact conn [[:skirmish.ledger/create-security sec-id interest-rate]])) \ No newline at end of file diff --git a/src/clj/skirmish/ledger/routes.clj b/src/clj/skirmish/ledger/routes.clj new file mode 100644 index 0000000..e6ecd2b --- /dev/null +++ b/src/clj/skirmish/ledger/routes.clj @@ -0,0 +1,183 @@ +(ns skirmish.ledger.routes + (:require [skirmish.rest.util :refer [build-response get-body cors-middleware]] + [skirmish.ledger.data-layer :as data-layer] + [skirmish.routes.core :as routes] + [compojure.core :as compojure] + [compojure.route :refer [not-found]] + [clojure.spec.alpha :as s] + [datomic.api :as d] + [slingshot.slingshot :refer [try+ throw+]])) + + + +(defn get-api-description [request] + (let [body [{:url "ledger/accounts" :name "accounts"} + {:url "ledger/securities" :name "securities"}]] + (build-response request body))) + +(defn get-available-accounts-body [accounts] + (let [sec-id->url #(routes/get-path-for :ledger/account {:account-id (name %)}) + to-security-description #(hash-map :account-public-key % :url (sec-id->url %)) + xf (comp cat (map to-security-description)) + securities-info (sequence xf accounts)] + {:accounts securities-info})) + + +(defn get-available-accounts [conn] + (fn [request] + (let [db (d/db conn) + resp (get-available-accounts-body (data-layer/get-all-account-public-key db))] + (build-response request resp)))) + +(defn get-account-body [request {sec-id :sec-id amount :amount account-public-key :account-public-key}] + (if (nil? sec-id) + (build-response request {:status 404}) + (let [sec-url (str "/api/1/ledger/securities/" (name sec-id)) + beautify-account { :amount amount + :sec-id sec-id + :account-public-key account-public-key} + ret {:account beautify-account :sec-url sec-url}] + (build-response request ret)))) + +(defn get-account [conn] + "Returns the value of an account." + (fn [{{account-id :account-id} :route-params :as request}] + (let [db (d/db conn) + account-info (data-layer/get-amount-in-account db account-id)] + (get-account-body request account-info)))) + +(defn get-account [conn] + "Returns the value of an account." + (fn [{{account-id :account-id} :route-params :as request}] + (let [db (d/db conn) + account-info (data-layer/get-amount-in-account db account-id)] + (get-account-body request account-info)))) + +(defn get-transactions [conn] + (fn [request] + (let [all-pending-transactions []] + (build-response request all-pending-transactions)))) + +(defn create-transactions [conn] + (fn [{body :body :as request}] + (try+ + (build-response request {:message "merci beaucoup"}) + (catch [:error-type :4XX] e (build-response request e)) + (catch [:error-type :5XX] e (build-response request e))))) + +(defn get-available-securities-body [securities] + (let [sec-id->url #(routes/get-path-for :ledger/security {:sec-id (name %)}) + to-security-description #(hash-map :sec-id % :url (sec-id->url %)) + xf (comp cat (map to-security-description)) + securities-info (sequence xf securities)];; + {:securities securities-info})) + + +(defn get-available-securities [conn] + (fn [request] + (let [db (d/db conn) + securities (data-layer/get-all-securities-id db)] + (build-response request (get-available-securities-body securities))))) + +(defn get-new-security [raw-securities] + (if (s/valid? :skirmish.ledger.core/security raw-securities) + raw-securities + (throw+ {:error-type :4XX :status 400 :body {:error "Invalid security format."}}))) + +(defn create-security [conn] + "Create a fresh new security." + (fn [{body :body :as request}] + (try+ + (let [[sec-id interest-rate] (-> body get-body get-new-security) + new-sec @(data-layer/create-security conn sec-id interest-rate)] + (build-response request {:body {:success true} + :headers {"location" (str "/api/1" "/ledger/securities/" (name sec-id))}})) + (catch [:error-type :4XX] e (build-response request e)) + (catch [:error-type :5XX] e (build-response request e))))) + + +(defn prettify-security [e db] + (let [{interest-rate :skirmish.security/interest-rate + description :skirmish.security/description + id :db/id + sec-id :skirmish.security/sec-id + symbol :skirmish.security/symbol + name :skirmish.security/name + number-of-decimal :skirmish.security/number-of-decimal + type_ :skirmish.security/type} e + interest-rate-history (data-layer/get-interest-rate-history db id) + volumes (d/q '[:find ?amount ?date + :in $ ?id + :where [?e :skirmish.ledger/sec ?id] + [?e :skirmish.ledger/amount ?amount ?tx] + [?tx :db/txInstant ?date]] + db id) + now (new java.util.Date) + volume (reduce + 0 (map #(data-layer/compute-face-value (first %) interest-rate-history (second %) now) volumes))] + {:sec-id sec-id + :interest-rate interest-rate + :volume (or volume 0) + :name name + :interest-rate-history interest-rate-history + :symbol symbol + :number-of-decimal number-of-decimal + :type type_ + :description description})) + +(defn get-security-from-db [conn sec-id] + (let [db (d/db conn) + e (d/pull db '[*] [:skirmish.security/sec-id sec-id])] + (if (-> e :db/id nil?) + (throw+ {:error-type :4XX :status 404 :body {:error "Security not found."}}) + (prettify-security e db)))) + +(defn get-security [conn] + "Returns the security." + (fn [{{sec-id-str :sec-id} :route-params :as request}] + (try+ + (let [sec-id (keyword sec-id-str)] + (build-response request (get-security-from-db conn sec-id))) + (catch [:error-type :4XX] e (build-response request e)) + (catch [:error-type :5XX] e (build-response request e))))) + +(defn update-security [conn] + "Update a security with new features." + (fn [request] + (try+ + (catch [:error-type :4XX] e (build-response request e)) + (catch [:error-type :5XX] e (build-response request e))))) + +(defn get-emit-securities-request [[amount account] sec-id] + (if (s/valid? :skirmish.ledger.core/emit-securities [sec-id amount account]) + [:skirmish.ledger/emit-securities sec-id amount account] + (throw+ {:error-type :4XX :status 400 :body {:error "Invalid emit request."}}))) + + +(defn emit-securities [conn] + "Emit securities in the specified wallet. This is the only way to + create securities. + + payload + [:amount :account]" + (fn [{{sec-id-str :sec-id} :route-params body :body :as request}] + (try+ + (let [emit-request (-> body get-body (get-emit-securities-request (keyword sec-id-str)))] + @(d/transact conn [emit-request]) + (build-response request {})) + (catch [:error-type :4XX] e (build-response request e)) + (catch [:error-type :5XX] e (build-response request e))))) + +(defn create-routes-for-ledger [conn] + (compojure/routes + (routes/context :ledger [] + (routes/GET :ledger/home [] get-api-description) + (routes/GET :ledger/accounts [] (get-available-accounts conn)) + (routes/GET :ledger/account [] (get-account conn)) + (routes/GET :ledger/transactions [] (get-transactions conn)) + (routes/POST :ledger/transactions [] (create-transactions conn)) + (routes/GET :ledger/securities [] (get-available-securities conn)) + (routes/POST :ledger/securities [] (create-security conn)) + (routes/GET :ledger/security [] (get-security conn)) + (routes/PUT :ledger/security [] (update-security conn))) ;; not done + ;(routes/POST :ledger/security [] (emit-securities conn))) ;; done + (not-found "not found"))) \ No newline at end of file diff --git a/test/clj/skirmish/ledger/core_test.clj b/test/clj/skirmish/ledger/core_test.clj deleted file mode 100644 index eb27f93..0000000 --- a/test/clj/skirmish/ledger/core_test.clj +++ /dev/null @@ -1,6 +0,0 @@ -(ns skirmish.ledger.core-test - (:require [clojure.test :refer :all]) - (:require [skirmish.ledger.core :refer [get-amount-in-account]])) - -(deftest get-amount-in-account-test) - diff --git a/test/clj/skirmish/ledger/test_core.clj b/test/clj/skirmish/ledger/test_core.clj index c2c69b0..255c7d9 100644 --- a/test/clj/skirmish/ledger/test_core.clj +++ b/test/clj/skirmish/ledger/test_core.clj @@ -1,13 +1,15 @@ (ns skirmish.ledger.test-core (:require [midje.sweet :refer :all] - [datomic.api :as d] - [clojure.edn] + [skirmish.ledger.data-layer :as data-layer] + [skirmish.ledger.accounts :refer [create-connection schema]] [skirmish.rest.util :refer [build-response]] [skirmish.ledger.core :refer :all] [skirmish.rest.util :refer [edn->stream]] + [datomic.api :as d] + [clojure.edn] [clojure.spec.alpha :as s] - [infix.macros :refer [infix]] - [skirmish.ledger.accounts :refer [create-connection schema]])) + [infix.macros :refer [infix]])) + (defn spec-check [spec] #(s/valid? spec %)) @@ -208,11 +210,11 @@ start #inst"2020-09-05T00:00:00.000-00:00" start+1d #inst"2020-09-06T00:00:00.000-00:00" start+2d #inst"2020-09-07T00:00:00.000-00:00"] - (compute-simple-face-value amount interest-rate start start) => (roughly amount) - (compute-simple-face-value amount interest-rate start start+1d) => (roughly 1105.1670808) - (compute-simple-face-value amount interest-rate start start+2d) => (roughly 1221.3942766) - (compute-simple-face-value amount negative-interest-rate start start+2d) => (roughly 818.7250672) - (compute-simple-face-value amount 0 start start+2d) => (roughly amount))) + (data-layer/compute-simple-face-value amount interest-rate start start) => (roughly amount) + (data-layer/compute-simple-face-value amount interest-rate start start+1d) => (roughly 1105.1670808) + (data-layer/compute-simple-face-value amount interest-rate start start+2d) => (roughly 1221.3942766) + (data-layer/compute-simple-face-value amount negative-interest-rate start start+2d) => (roughly 818.7250672) + (data-layer/compute-simple-face-value amount 0 start start+2d) => (roughly amount))) (fact "split-interest-rates" (let [d-1d #inst"2020-09-04T00:00:00.000-00:00" d-½d #inst"2020-09-04T12:00:00.000-00:00" @@ -225,11 +227,11 @@ d+3d #inst"2020-09-08T00:00:00.000-00:00" d+3½d #inst"2020-09-08T12:00:00.000-00:00";; interest-rates [[10 d] [15 d+1d] [20 d+2d]]] - (split-interest-rates interest-rates d+½d d+3d) => [[10 d+½d d+1d] [15 d+1d d+2d] [20 d+2d d+3d]] - (split-interest-rates interest-rates d+3d d+3½d) => [[20 d+3d d+3½d]] - (split-interest-rates interest-rates d-1d d-½d) => [[0 d-1d d-½d]] - (split-interest-rates interest-rates d d+½d) => [[10 d d+½d]] - (split-interest-rates interest-rates d+2½d d+2½d) => [[20 d+2½d d+2½d]])) + (data-layer/split-interest-rates interest-rates d+½d d+3d) => [[10 d+½d d+1d] [15 d+1d d+2d] [20 d+2d d+3d]] + (data-layer/split-interest-rates interest-rates d+3d d+3½d) => [[20 d+3d d+3½d]] + (data-layer/split-interest-rates interest-rates d-1d d-½d) => [[0 d-1d d-½d]] + (data-layer/split-interest-rates interest-rates d d+½d) => [[10 d d+½d]] + (data-layer/split-interest-rates interest-rates d+2½d d+2½d) => [[20 d+2½d d+2½d]])) (fact "compute-face-value" (let [d-1d #inst"2020-09-04T00:00:00.000-00:00" d-½d #inst"2020-09-04T12:00:00.000-00:00" @@ -243,15 +245,15 @@ d+3½d #inst"2020-09-08T12:00:00.000-00:00" amount 10000000 interest-rates [[10 d] [15 d+1d] [20 d+2d]]] - (compute-face-value amount interest-rates d+½d d+½d) => amount - (compute-face-value amount interest-rates d-1d d-½d) => amount - (compute-face-value amount interest-rates d-1d d+3d) => 10045101)) + (data-layer/compute-face-value amount interest-rates d+½d d+½d) => amount + (data-layer/compute-face-value amount interest-rates d-1d d-½d) => amount + (data-layer/compute-face-value amount interest-rates d-1d d+3d) => 10045101)) (fact "we had bugs... we need to fix them" (let [deposit-date #inst"2020-09-09T18:49:47.205-00:00" value-date #inst"2020-09-09T18:52:56.760-00:00" amount 105402100 interest-rates (list [1000 #inst "2020-09-09T18:47:34.152-00:00"])] - (compute-face-value amount interest-rates deposit-date value-date) => 105424060))) + (data-layer/compute-face-value amount interest-rates deposit-date value-date) => 105424060))) ;; 4. transfer sec from one account to the other.