From 8b919f4058b22fba56763fc9c50ebc0f282e4a06 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:40:26 -0600 Subject: [PATCH] [#17896] Raw balances using big number (#17920) * Add `utils.money/add` version able to work with `nil`s * Store token raw-balance as big-number * Improve account balance calculations using `utils.money` existing functions * Update `:wallet/tokens-filtered` sub * Make `prettify-balance` able to work with bignumbers --- .../components/wallet/account_card/view.cljs | 4 +- .../contexts/wallet/common/utils.cljs | 45 +++++++---- src/status_im2/contexts/wallet/events.cljs | 9 ++- src/status_im2/subs/wallet/wallet.cljs | 29 +++---- src/status_im2/subs/wallet/wallet_test.cljs | 79 ++++++++++--------- src/utils/money.cljs | 8 +- 6 files changed, 100 insertions(+), 74 deletions(-) diff --git a/src/quo/components/wallet/account_card/view.cljs b/src/quo/components/wallet/account_card/view.cljs index 7f86daa5aa..d15b2e1b25 100644 --- a/src/quo/components/wallet/account_card/view.cljs +++ b/src/quo/components/wallet/account_card/view.cljs @@ -93,7 +93,7 @@ :end {:x 1 :y 0}}]) (defn- user-account - [] + [_] (let [pressed? (reagent/atom false) on-press-in #(reset! pressed? true) on-press-out #(reset! pressed? false)] @@ -147,7 +147,7 @@ [gradient-overview theme customization-color])]))))) (defn- add-account-view - [] + [_] (let [pressed? (reagent/atom false)] (fn [{:keys [on-press customization-color theme metrics?]}] [rn/pressable diff --git a/src/status_im2/contexts/wallet/common/utils.cljs b/src/status_im2/contexts/wallet/common/utils.cljs index 9bca9454a9..9d0070fbee 100644 --- a/src/status_im2/contexts/wallet/common/utils.cljs +++ b/src/status_im2/contexts/wallet/common/utils.cljs @@ -10,7 +10,12 @@ (defn prettify-balance [balance] - (str "$" (.toFixed (if (number? balance) balance 0) 2))) + (let [valid-balance? (and balance + (or (number? balance) (.-toFixed balance)))] + (as-> balance $ + (if valid-balance? $ 0) + (.toFixed $ 2) + (str "$" $)))) (defn get-derivation-path [number-of-accounts] @@ -25,32 +30,42 @@ (let [path (get-derivation-path number-of-accounts)] (format-derivation-path path))) +(defn- total-raw-balance-in-all-chains + [balances-per-chain] + (->> balances-per-chain + (map (comp :raw-balance val)) + (reduce money/add))) + +(defn total-token-units-in-all-chains + [{:keys [balances-per-chain decimals] :as _token}] + (-> balances-per-chain + (total-raw-balance-in-all-chains) + (money/token->unit decimals))) + (defn calculate-raw-balance [raw-balance decimals] (if-let [n (utils.number/parse-int raw-balance nil)] (/ n (Math/pow 10 (utils.number/parse-int decimals))) 0)) -(defn total-token-value-in-all-chains - [{:keys [balances-per-chain decimals]}] - (->> balances-per-chain - (vals) - (map #(calculate-raw-balance (:raw-balance %) decimals)) - (reduce +))) - (defn token-value-in-chain [{:keys [balances-per-chain decimals]} chain-id] (let [balance-in-chain (get balances-per-chain chain-id)] (when balance-in-chain (calculate-raw-balance (:raw-balance balance-in-chain) decimals)))) -(defn calculate-balance - [tokens-in-account] - (->> tokens-in-account - (map (fn [token] - (* (total-token-value-in-all-chains token) - (-> token :market-values-per-currency :usd :price)))) - (reduce +))) +(defn total-token-fiat-value + "Returns the total token fiat value taking into account all token's chains." + [{:keys [market-values-per-currency] :as token}] + (let [usd-price (-> market-values-per-currency :usd :price) + total-units-in-all-chains (total-token-units-in-all-chains token)] + (money/crypto->fiat total-units-in-all-chains usd-price))) + +(defn calculate-balance-for-account + [{:keys [tokens] :as _account}] + (->> tokens + (map total-token-fiat-value) + (reduce money/add))) (defn network-list [{:keys [balances-per-chain]} networks] diff --git a/src/status_im2/contexts/wallet/events.cljs b/src/status_im2/contexts/wallet/events.cljs index db959d5558..f0480715f4 100644 --- a/src/status_im2/contexts/wallet/events.cljs +++ b/src/status_im2/contexts/wallet/events.cljs @@ -12,6 +12,7 @@ [utils.ethereum.chain :as chain] [utils.ethereum.eip.eip55 :as eip55] [utils.i18n :as i18n] + [utils.money :as money] [utils.number] [utils.re-frame :as rf] [utils.transforms :as types])) @@ -108,9 +109,11 @@ :event :wallet/get-wallet-token :params addresses})}]]]}))) -(defn- fix-chain-id-keys +(defn- fix-balances-per-chain [token] - (update token :balances-per-chain update-keys (comp utils.number/parse-int name))) + (-> token + (update :balances-per-chain update-vals #(update % :raw-balance money/bignumber)) + (update :balances-per-chain update-keys (comp utils.number/parse-int name)))) (rf/reg-event-fx :wallet/store-wallet-token @@ -118,7 +121,7 @@ (let [tokens (-> raw-tokens-data (update-keys name) (update-vals #(cske/transform-keys csk/->kebab-case %)) - (update-vals #(mapv fix-chain-id-keys %))) + (update-vals #(mapv fix-balances-per-chain %))) add-tokens (fn [stored-accounts tokens-per-account] (reduce-kv (fn [accounts address tokens-data] (if (accounts address) diff --git a/src/status_im2/subs/wallet/wallet.cljs b/src/status_im2/subs/wallet/wallet.cljs index 2f7fe983ba..81c7a9e0ec 100644 --- a/src/status_im2/subs/wallet/wallet.cljs +++ b/src/status_im2/subs/wallet/wallet.cljs @@ -40,7 +40,7 @@ :<- [:wallet/accounts] (fn [accounts] (zipmap (map :address accounts) - (map #(-> % :tokens utils/calculate-balance) accounts)))) + (map utils/calculate-balance-for-account accounts)))) (rf/reg-sub :wallet/account-cards-data @@ -71,21 +71,18 @@ :<- [:wallet/current-viewing-account] :<- [:wallet/network-details] (fn [[account networks] [_ query]] - (let [tokens (map (fn [token] - (assoc token - :networks (utils/network-list token networks) - :total-balance (utils/total-token-value-in-all-chains token) - :total-balance-fiat (utils/calculate-balance token))) - (:tokens account)) - - sorted-tokens - (sort-by :name compare tokens) - filtered-tokens - (filter #(or (string/starts-with? (string/lower-case (:name %)) - (string/lower-case query)) - (string/starts-with? (string/lower-case (:symbol %)) - (string/lower-case query))) - sorted-tokens)] + (let [tokens (map (fn [token] + (assoc token + :networks (utils/network-list token networks) + :total-balance (utils/total-token-units-in-all-chains token) + :total-balance-fiat 0)) + (:tokens account)) + sorted-tokens (sort-by :name compare tokens) + filtered-tokens (filter #(or (string/starts-with? (string/lower-case (:name %)) + (string/lower-case query)) + (string/starts-with? (string/lower-case (:symbol %)) + (string/lower-case query))) + sorted-tokens)] filtered-tokens))) (rf/reg-sub diff --git a/src/status_im2/subs/wallet/wallet_test.cljs b/src/status_im2/subs/wallet/wallet_test.cljs index 1a2aa321b3..f884f33770 100644 --- a/src/status_im2/subs/wallet/wallet_test.cljs +++ b/src/status_im2/subs/wallet/wallet_test.cljs @@ -1,8 +1,9 @@ (ns status-im2.subs.wallet.wallet-test (:require [cljs.test :refer [is testing use-fixtures]] [re-frame.db :as rf-db] - status-im2.subs.root + [status-im2.subs.root] [test-helpers.unit :as h] + [utils.money :as money] [utils.re-frame :as rf])) (use-fixtures :each @@ -12,30 +13,31 @@ [{:decimals 1 :symbol "ETH" :name "Ether" - :balances-per-chain {1 {:raw-balance "20" :has-error false} - 2 {:raw-balance "10" :has-error false}} + :balances-per-chain {1 {:raw-balance (money/bignumber "20") :has-error false} + 2 {:raw-balance (money/bignumber "10") :has-error false}} :market-values-per-currency {:usd {:price 1000}}} {:decimals 2 :symbol "DAI" :name "Dai Stablecoin" - :balances-per-chain {1 {:raw-balance "100" :has-error false} - 2 {:raw-balance "150" :has-error false}} + :balances-per-chain {1 {:raw-balance (money/bignumber "100") :has-error false} + 2 {:raw-balance (money/bignumber "150") :has-error false} + 3 {:raw-balance nil :has-error false}} :market-values-per-currency {:usd {:price 100}}}]) (def tokens-0x2 [{:decimals 3 :symbol "ETH" :name "Ether" - :balances-per-chain {1 {:raw-balance "2500" :has-error false} - 2 {:raw-balance "3000" :has-error false} - 3 {:raw-balance "" :has-error false}} + :balances-per-chain {1 {:raw-balance (money/bignumber "2500") :has-error false} + 2 {:raw-balance (money/bignumber "3000") :has-error false} + 3 {:raw-balance (money/bignumber "") :has-error false}} :market-values-per-currency {:usd {:price 200}}} {:decimals 10 :symbol "DAI" :name "Dai Stablecoin" - :balances-per-chain {1 {:raw-balance "10000000000" :has-error false} - 2 {:raw-balance "0" :has-error false} - 3 {:raw-balance "" :has-error false}} + :balances-per-chain {1 {:raw-balance (money/bignumber "10000000000") :has-error false} + 2 {:raw-balance (money/bignumber "0") :has-error false} + 3 {:raw-balance (money/bignumber "") :has-error false}} :market-values-per-currency {:usd {:price 1000}}}]) (def accounts @@ -84,9 +86,12 @@ [sub-name] (testing "a map: address->balance" (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) + (let [result (rf/sub [sub-name]) + balance-0x1 (money/bignumber 3250) + balance-0x2 (money/bignumber 2100)] - (is (= {"0x1" 3250 "0x2" 2100} - (rf/sub [sub-name]))))) + (is (money/equal-to balance-0x1 (get result "0x1"))) + (is (money/equal-to balance-0x2 (get result "0x2")))))) (h/deftest-sub :wallet/accounts [sub-name] @@ -143,30 +148,32 @@ #(-> % (assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :current-viewing-account-address] "0x1"))) + (let [result (rf/sub [sub-name])] - (is - (= {:path "m/44'/60'/0'/0/0" - :emoji "😃" - :key-uid "0x2f5ea39" - :address "0x1" - :wallet false - :name "Account One" - :type :generated - :chat false - :test-preferred-chain-ids #{5 420 421613} - :color :blue - :hidden false - :prod-preferred-chain-ids #{1 10 42161} - :position 0 - :clock 1698945829328 - :created-at 1698928839000 - :operable "fully" - :mixedcase-address "0x7bcDfc75c431" - :public-key "0x04371e2d9d66b82f056bc128064" - :removed false - :balance 3250 - :tokens tokens-0x1} - (rf/sub [sub-name]))))) + (is + (= {:path "m/44'/60'/0'/0/0" + :emoji "😃" + :key-uid "0x2f5ea39" + :address "0x1" + :wallet false + :name "Account One" + :type :generated + :chat false + :test-preferred-chain-ids #{5 420 421613} + :color :blue + :hidden false + :prod-preferred-chain-ids #{1 10 42161} + :position 0 + :clock 1698945829328 + :created-at 1698928839000 + :operable "fully" + :mixedcase-address "0x7bcDfc75c431" + :public-key "0x04371e2d9d66b82f056bc128064" + :removed false + :tokens tokens-0x1} + (dissoc result :balance))) + + (is (money/equal-to (:balance result) (money/bignumber 3250)))))) (h/deftest-sub :wallet/addresses diff --git a/src/utils/money.cljs b/src/utils/money.cljs index 68e5a07baa..e70bb4eb14 100644 --- a/src/utils/money.cljs +++ b/src/utils/money.cljs @@ -152,7 +152,7 @@ ;; E.g. for Ether, it's smallest part is wei or 10^(-18) of 1 ether ;; for arbitrary ERC20 token the smallest part is 10^(-decimals) of 1 token ;; -;; Different tokens can have different number of allowed decimals, so it's neccessary to include the +;; Different tokens can have different number of allowed decimals, so it's necessary to include the ;; decimals parameter ;; to get the amount scale right. @@ -205,10 +205,14 @@ (with-precision 2) str)) -(defn add +(defn- add* [bn1 n2] (.add ^js bn1 n2)) +(def add + "Add with defaults, this version is able to receive `nil` and takes them as 0." + (fnil add* (bignumber 0) (bignumber 0))) + (defn mul [bn1 bn2] (.mul ^js bn1 bn2))