[#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}}])
(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

View File

@ -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]

View File

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

View File

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

View File

@ -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 "<nil>" :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 "<nil>") :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 "<nil>" :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 "<nil>") :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

View File

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