[#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
This commit is contained in:
Ulises Manuel 2023-12-08 13:40:26 -06:00 committed by GitHub
parent f79860fb97
commit 8b919f4058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 74 deletions

View File

@ -93,7 +93,7 @@
:end {:x 1 :y 0}}]) :end {:x 1 :y 0}}])
(defn- user-account (defn- user-account
[] [_]
(let [pressed? (reagent/atom false) (let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true) on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)] on-press-out #(reset! pressed? false)]
@ -147,7 +147,7 @@
[gradient-overview theme customization-color])]))))) [gradient-overview theme customization-color])])))))
(defn- add-account-view (defn- add-account-view
[] [_]
(let [pressed? (reagent/atom false)] (let [pressed? (reagent/atom false)]
(fn [{:keys [on-press customization-color theme metrics?]}] (fn [{:keys [on-press customization-color theme metrics?]}]
[rn/pressable [rn/pressable

View File

@ -10,7 +10,12 @@
(defn prettify-balance (defn prettify-balance
[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 (defn get-derivation-path
[number-of-accounts] [number-of-accounts]
@ -25,32 +30,42 @@
(let [path (get-derivation-path number-of-accounts)] (let [path (get-derivation-path number-of-accounts)]
(format-derivation-path path))) (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 (defn calculate-raw-balance
[raw-balance decimals] [raw-balance decimals]
(if-let [n (utils.number/parse-int raw-balance nil)] (if-let [n (utils.number/parse-int raw-balance nil)]
(/ n (Math/pow 10 (utils.number/parse-int decimals))) (/ n (Math/pow 10 (utils.number/parse-int decimals)))
0)) 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 (defn token-value-in-chain
[{:keys [balances-per-chain decimals]} chain-id] [{:keys [balances-per-chain decimals]} chain-id]
(let [balance-in-chain (get balances-per-chain chain-id)] (let [balance-in-chain (get balances-per-chain chain-id)]
(when balance-in-chain (when balance-in-chain
(calculate-raw-balance (:raw-balance balance-in-chain) decimals)))) (calculate-raw-balance (:raw-balance balance-in-chain) decimals))))
(defn calculate-balance (defn total-token-fiat-value
[tokens-in-account] "Returns the total token fiat value taking into account all token's chains."
(->> tokens-in-account [{:keys [market-values-per-currency] :as token}]
(map (fn [token] (let [usd-price (-> market-values-per-currency :usd :price)
(* (total-token-value-in-all-chains token) total-units-in-all-chains (total-token-units-in-all-chains token)]
(-> token :market-values-per-currency :usd :price)))) (money/crypto->fiat total-units-in-all-chains usd-price)))
(reduce +)))
(defn calculate-balance-for-account
[{:keys [tokens] :as _account}]
(->> tokens
(map total-token-fiat-value)
(reduce money/add)))
(defn network-list (defn network-list
[{:keys [balances-per-chain]} networks] [{:keys [balances-per-chain]} networks]

View File

@ -12,6 +12,7 @@
[utils.ethereum.chain :as chain] [utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55] [utils.ethereum.eip.eip55 :as eip55]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.money :as money]
[utils.number] [utils.number]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.transforms :as types])) [utils.transforms :as types]))
@ -108,9 +109,11 @@
:event :wallet/get-wallet-token :event :wallet/get-wallet-token
:params addresses})}]]]}))) :params addresses})}]]]})))
(defn- fix-chain-id-keys (defn- fix-balances-per-chain
[token] [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 (rf/reg-event-fx
:wallet/store-wallet-token :wallet/store-wallet-token
@ -118,7 +121,7 @@
(let [tokens (-> raw-tokens-data (let [tokens (-> raw-tokens-data
(update-keys name) (update-keys name)
(update-vals #(cske/transform-keys csk/->kebab-case %)) (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] add-tokens (fn [stored-accounts tokens-per-account]
(reduce-kv (fn [accounts address tokens-data] (reduce-kv (fn [accounts address tokens-data]
(if (accounts address) (if (accounts address)

View File

@ -40,7 +40,7 @@
:<- [:wallet/accounts] :<- [:wallet/accounts]
(fn [accounts] (fn [accounts]
(zipmap (map :address accounts) (zipmap (map :address accounts)
(map #(-> % :tokens utils/calculate-balance) accounts)))) (map utils/calculate-balance-for-account accounts))))
(rf/reg-sub (rf/reg-sub
:wallet/account-cards-data :wallet/account-cards-data
@ -71,21 +71,18 @@
:<- [:wallet/current-viewing-account] :<- [:wallet/current-viewing-account]
:<- [:wallet/network-details] :<- [:wallet/network-details]
(fn [[account networks] [_ query]] (fn [[account networks] [_ query]]
(let [tokens (map (fn [token] (let [tokens (map (fn [token]
(assoc token (assoc token
:networks (utils/network-list token networks) :networks (utils/network-list token networks)
:total-balance (utils/total-token-value-in-all-chains token) :total-balance (utils/total-token-units-in-all-chains token)
:total-balance-fiat (utils/calculate-balance token))) :total-balance-fiat 0))
(:tokens account)) (:tokens account))
sorted-tokens (sort-by :name compare tokens)
sorted-tokens filtered-tokens (filter #(or (string/starts-with? (string/lower-case (:name %))
(sort-by :name compare tokens) (string/lower-case query))
filtered-tokens (string/starts-with? (string/lower-case (:symbol %))
(filter #(or (string/starts-with? (string/lower-case (:name %)) (string/lower-case query)))
(string/lower-case query)) sorted-tokens)]
(string/starts-with? (string/lower-case (:symbol %))
(string/lower-case query)))
sorted-tokens)]
filtered-tokens))) filtered-tokens)))
(rf/reg-sub (rf/reg-sub

View File

@ -1,8 +1,9 @@
(ns status-im2.subs.wallet.wallet-test (ns status-im2.subs.wallet.wallet-test
(:require [cljs.test :refer [is testing use-fixtures]] (:require [cljs.test :refer [is testing use-fixtures]]
[re-frame.db :as rf-db] [re-frame.db :as rf-db]
status-im2.subs.root [status-im2.subs.root]
[test-helpers.unit :as h] [test-helpers.unit :as h]
[utils.money :as money]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(use-fixtures :each (use-fixtures :each
@ -12,30 +13,31 @@
[{:decimals 1 [{:decimals 1
:symbol "ETH" :symbol "ETH"
:name "Ether" :name "Ether"
:balances-per-chain {1 {:raw-balance "20" :has-error false} :balances-per-chain {1 {:raw-balance (money/bignumber "20") :has-error false}
2 {:raw-balance "10" :has-error false}} 2 {:raw-balance (money/bignumber "10") :has-error false}}
:market-values-per-currency {:usd {:price 1000}}} :market-values-per-currency {:usd {:price 1000}}}
{:decimals 2 {:decimals 2
:symbol "DAI" :symbol "DAI"
:name "Dai Stablecoin" :name "Dai Stablecoin"
:balances-per-chain {1 {:raw-balance "100" :has-error false} :balances-per-chain {1 {:raw-balance (money/bignumber "100") :has-error false}
2 {:raw-balance "150" :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}}}]) :market-values-per-currency {:usd {:price 100}}}])
(def tokens-0x2 (def tokens-0x2
[{:decimals 3 [{:decimals 3
:symbol "ETH" :symbol "ETH"
:name "Ether" :name "Ether"
:balances-per-chain {1 {:raw-balance "2500" :has-error false} :balances-per-chain {1 {:raw-balance (money/bignumber "2500") :has-error false}
2 {:raw-balance "3000" :has-error false} 2 {:raw-balance (money/bignumber "3000") :has-error false}
3 {:raw-balance "<nil>" :has-error false}} 3 {:raw-balance (money/bignumber "<nil>") :has-error false}}
:market-values-per-currency {:usd {:price 200}}} :market-values-per-currency {:usd {:price 200}}}
{:decimals 10 {:decimals 10
:symbol "DAI" :symbol "DAI"
:name "Dai Stablecoin" :name "Dai Stablecoin"
:balances-per-chain {1 {:raw-balance "10000000000" :has-error false} :balances-per-chain {1 {:raw-balance (money/bignumber "10000000000") :has-error false}
2 {:raw-balance "0" :has-error false} 2 {:raw-balance (money/bignumber "0") :has-error false}
3 {:raw-balance "<nil>" :has-error false}} 3 {:raw-balance (money/bignumber "<nil>") :has-error false}}
:market-values-per-currency {:usd {:price 1000}}}]) :market-values-per-currency {:usd {:price 1000}}}])
(def accounts (def accounts
@ -84,9 +86,12 @@
[sub-name] [sub-name]
(testing "a map: address->balance" (testing "a map: address->balance"
(swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) (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} (is (money/equal-to balance-0x1 (get result "0x1")))
(rf/sub [sub-name]))))) (is (money/equal-to balance-0x2 (get result "0x2"))))))
(h/deftest-sub :wallet/accounts (h/deftest-sub :wallet/accounts
[sub-name] [sub-name]
@ -143,30 +148,32 @@
#(-> % #(-> %
(assoc-in [:wallet :accounts] accounts) (assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1"))) (assoc-in [:wallet :current-viewing-account-address] "0x1")))
(let [result (rf/sub [sub-name])]
(is (is
(= {:path "m/44'/60'/0'/0/0" (= {:path "m/44'/60'/0'/0/0"
:emoji "😃" :emoji "😃"
:key-uid "0x2f5ea39" :key-uid "0x2f5ea39"
:address "0x1" :address "0x1"
:wallet false :wallet false
:name "Account One" :name "Account One"
:type :generated :type :generated
:chat false :chat false
:test-preferred-chain-ids #{5 420 421613} :test-preferred-chain-ids #{5 420 421613}
:color :blue :color :blue
:hidden false :hidden false
:prod-preferred-chain-ids #{1 10 42161} :prod-preferred-chain-ids #{1 10 42161}
:position 0 :position 0
:clock 1698945829328 :clock 1698945829328
:created-at 1698928839000 :created-at 1698928839000
:operable "fully" :operable "fully"
:mixedcase-address "0x7bcDfc75c431" :mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064" :public-key "0x04371e2d9d66b82f056bc128064"
:removed false :removed false
:balance 3250 :tokens tokens-0x1}
:tokens tokens-0x1} (dissoc result :balance)))
(rf/sub [sub-name])))))
(is (money/equal-to (:balance result) (money/bignumber 3250))))))
(h/deftest-sub :wallet/addresses (h/deftest-sub :wallet/addresses

View File

@ -152,7 +152,7 @@
;; E.g. for Ether, it's smallest part is wei or 10^(-18) of 1 ether ;; 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 ;; 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 ;; decimals parameter
;; to get the amount scale right. ;; to get the amount scale right.
@ -205,10 +205,14 @@
(with-precision 2) (with-precision 2)
str)) str))
(defn add (defn- add*
[bn1 n2] [bn1 n2]
(.add ^js 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 (defn mul
[bn1 bn2] [bn1 bn2]
(.mul ^js bn1 bn2)) (.mul ^js bn1 bn2))