bug #4123 - fixes incorrect number of decimals for tokens, fixes incorrect network id lookup for token send, fixes token amount formatting where needed

This commit is contained in:
Goran Jovic 2018-05-24 12:58:39 +02:00 committed by Roman Volosovskyi
parent ae56eec40f
commit 5e33c98008
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
11 changed files with 87 additions and 42 deletions

View File

@ -14,7 +14,7 @@
(defn send-shortcut-fx [db contact params] (defn send-shortcut-fx [db contact params]
(merge {:db (-> db (merge {:db (-> db
(send.events/set-and-validate-amount-db (:amount params)) (send.events/set-and-validate-amount-db (:amount params) :ETH 18)
(choose-recipient.events/fill-request-details (transaction-details contact)) (choose-recipient.events/fill-request-details (transaction-details contact))
(navigation/navigate-to :wallet-send-transaction-chat))} (navigation/navigate-to :wallet-send-transaction-chat))}
(send.events/update-gas-price db false))) (send.events/update-gas-price db false)))

View File

@ -86,3 +86,7 @@
(fn [db [_ item-id]] (fn [db [_ item-id]]
(let [item-animation (get-in db [:chat-animations item-id])] (let [item-animation (get-in db [:chat-animations item-id])]
(if (some? item-animation) (:delete-swiped item-animation) nil)))) (if (some? item-animation) (:delete-swiped item-animation) nil))))
(reg-sub :get-current-account-network
(fn [{:keys [network] :as db} [_]]
(get-in db [:account/account :networks network])))

View File

@ -185,7 +185,8 @@
[recipient-contact address name request?] [recipient-contact address name request?]
[recipient-address address])]]) [recipient-address address])]])
(defn- amount-input [{:keys [input-options amount amount-text disabled?]}] (defn- amount-input [{:keys [input-options amount amount-text disabled?]}
{:keys [symbol decimals]}]
[react/view {:style components.styles/flex [react/view {:style components.styles/flex
:accessibility-label :specify-amount-button} :accessibility-label :specify-amount-button}
[components/text-input [components/text-input
@ -195,7 +196,9 @@
;; Otherwise, user might want to fix his input and autocorrection will give more harm than good. ;; Otherwise, user might want to fix his input and autocorrection will give more harm than good.
;; Positive check is because we don't want to replace unfinished 0.000 with just plain 0, that is annoying and ;; Positive check is because we don't want to replace unfinished 0.000 with just plain 0, that is annoying and
;; potentially dangerous on this screen (e.g. sending 7 ETH instead of 0.0007) ;; potentially dangerous on this screen (e.g. sending 7 ETH instead of 0.0007)
{:default-value (if (empty? amount-text) (str (money/to-fixed (money/wei->ether amount))) amount-text)} {:default-value (if (empty? amount-text)
(str (money/to-fixed (money/internal->formatted amount symbol decimals)))
amount-text)}
(if disabled? (if disabled?
{:editable false} {:editable false}
{:keyboard-type :numeric {:keyboard-type :numeric
@ -203,7 +206,7 @@
:style components.styles/flex :style components.styles/flex
:accessibility-label :amount-input}))]]) :accessibility-label :amount-input}))]])
(defn amount-selector [{:keys [error disabled?] :as m}] (defn amount-selector [{:keys [error disabled?] :as m} token]
[react/view [react/view
[components/cartouche {:disabled? disabled?} [components/cartouche {:disabled? disabled?}
(i18n/label :t/amount) (i18n/label :t/amount)

View File

@ -16,11 +16,11 @@
;; Placeholder namespace for wallet specs, which are a WIP depending on data ;; Placeholder namespace for wallet specs, which are a WIP depending on data
;; model we decide on for balances, prices, etc. ;; model we decide on for balances, prices, etc.
(defn- too-precise-amount? [amount] (defn- too-precise-amount? [amount decimals]
(let [amount-splited (string/split amount #"[.]")] (let [amount-splited (string/split amount #"[.]")]
(and (= (count amount-splited) 2) (> (count (last amount-splited)) 18)))) (and (= (count amount-splited) 2) (> (count (last amount-splited)) decimals))))
(defn parse-amount [amount] (defn parse-amount [amount decimals]
(when-not (empty? amount) (when-not (empty? amount)
(let [normalized-amount (money/normalize amount) (let [normalized-amount (money/normalize amount)
value (money/bignumber normalized-amount)] value (money/bignumber normalized-amount)]
@ -28,7 +28,7 @@
(not (money/valid? value)) (not (money/valid? value))
{:error (i18n/label :t/validation-amount-invalid-number) :value value} {:error (i18n/label :t/validation-amount-invalid-number) :value value}
(too-precise-amount? normalized-amount) (too-precise-amount? normalized-amount decimals)
{:error (i18n/label :t/validation-amount-is-too-precise) :value value} {:error (i18n/label :t/validation-amount-is-too-precise) :value value}
:else :else

View File

@ -35,7 +35,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.request/set-and-validate-amount :wallet.request/set-and-validate-amount
(fn [{:keys [db]} [_ amount]] (fn [{:keys [db]} [_ amount]]
(let [{:keys [value error]} (wallet-db/parse-amount amount)] (let [{:keys [value error]} (wallet-db/parse-amount amount :ETH)]
{:db (-> db {:db (-> db
(assoc-in [:wallet :request-transaction :amount] (money/ether->wei value)) (assoc-in [:wallet :request-transaction :amount] (money/ether->wei value))
(assoc-in [:wallet :request-transaction :amount-text] amount) (assoc-in [:wallet :request-transaction :amount-text] amount)

View File

@ -45,7 +45,9 @@
:amount-text amount-text :amount-text amount-text
:input-options {:max-length 21 :input-options {:max-length 21
:on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100))) :on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100)))
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}}]]] :on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}}
{:decimals 18
:symbol :ETH}]]]
[bottom-buttons/bottom-buttons styles/bottom-buttons [bottom-buttons/bottom-buttons styles/bottom-buttons
nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons
[button/button {:disabled? (not (and to amount)) [button/button {:disabled? (not (and to amount))

View File

@ -29,7 +29,7 @@
#())) #()))
(defn- send-tokens [{:keys [web3 from to value gas gas-price symbol network]}] (defn- send-tokens [{:keys [web3 from to value gas gas-price symbol network]}]
(let [contract (:address (tokens/symbol->token (ethereum/network->chain-keyword network) symbol))] (let [contract (:address (tokens/symbol->token (keyword (ethereum/network-names network)) symbol))]
(erc20/transfer web3 contract from to value {:gas gas :gasPrice gas-price} #()))) (erc20/transfer web3 contract from to value {:gas gas :gasPrice gas-price} #())))
(re-frame/reg-fx (re-frame/reg-fx
@ -68,22 +68,25 @@
(re-frame/dispatch [::transaction-completed {:id (name (key result)) :response (second result)} modal?])) (re-frame/dispatch [::transaction-completed {:id (name (key result)) :response (second result)} modal?]))
;;;; Handlers ;;;; Handlers
(defn set-and-validate-amount-db [db amount] (defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount)] (let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
(-> db (-> db
(assoc-in [:wallet :send-transaction :amount] (money/ether->wei value)) (assoc-in [:wallet :send-transaction :amount] (money/formatted->internal value symbol decimals))
(assoc-in [:wallet :send-transaction :amount-text] amount) (assoc-in [:wallet :send-transaction :amount-text] amount)
(assoc-in [:wallet :send-transaction :amount-error] error)))) (assoc-in [:wallet :send-transaction :amount-error] error))))
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.send/set-and-validate-amount :wallet.send/set-and-validate-amount
(fn [{:keys [db]} [_ amount]] (fn [{:keys [db]} [_ amount symbol decimals]]
{:db (set-and-validate-amount-db db amount)})) {:db (set-and-validate-amount-db db amount symbol decimals)}))
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.send/set-symbol :wallet.send/set-symbol
(fn [{:keys [db]} [_ symbol]] (fn [{:keys [db]} [_ symbol]]
{:db (-> (assoc-in db [:wallet :send-transaction :symbol] symbol) {:db (-> db
(assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :gas] (ethereum/estimate-gas symbol)))})) (assoc-in [:wallet :send-transaction :gas] (ethereum/estimate-gas symbol)))}))
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -21,7 +21,9 @@
[status-im.ui.screens.wallet.styles :as wallet.styles] [status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.transport.utils :as transport.utils])) [status-im.transport.utils :as transport.utils]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.ethereum.core :as ethereum]))
(defn sign-later-popup (defn sign-later-popup
[from-chat?] [from-chat?]
@ -192,8 +194,9 @@
(when advanced? (when advanced?
[advanced-cartouche transaction modal?])]) [advanced-cartouche transaction modal?])])
(defn- send-transaction-panel [{:keys [modal? transaction scroll advanced? symbol]}] (defn- send-transaction-panel [{:keys [modal? transaction scroll advanced? network]}]
(let [{:keys [amount amount-text amount-error signing? to to-name sufficient-funds? in-progress? from-chat?]} transaction (let [{:keys [amount amount-text amount-error signing? to to-name sufficient-funds? in-progress? from-chat? symbol]} transaction
{:keys [decimals] :as token} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)
timeout (atom nil)] timeout (atom nil)]
[wallet.components/simple-screen {:avoid-keyboard? (not modal?) [wallet.components/simple-screen {:avoid-keyboard? (not modal?)
:status-bar-type (if modal? :modal-wallet :wallet)} :status-bar-type (if modal? :modal-wallet :wallet)}
@ -219,7 +222,7 @@
:amount-text amount-text :amount-text amount-text
:input-options {:max-length 21 :input-options {:max-length 21
:on-focus (fn [] (when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 100))) :on-focus (fn [] (when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 100)))
:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount %])}}] :on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals])}} token]
[advanced-options advanced? transaction modal? scroll]]] [advanced-options advanced? transaction modal? scroll]]]
(if signing? (if signing?
[signing-buttons [signing-buttons
@ -244,16 +247,20 @@
(letsubs [transaction [:wallet.send/transaction] (letsubs [transaction [:wallet.send/transaction]
symbol [:wallet.send/symbol] symbol [:wallet.send/symbol]
advanced? [:wallet.send/advanced?] advanced? [:wallet.send/advanced?]
network [:get-current-account-network]
scroll (atom nil)] scroll (atom nil)]
[send-transaction-panel {:modal? false :transaction transaction :scroll scroll :advanced? advanced? :symbol symbol}])) [send-transaction-panel {:modal? false :transaction transaction :scroll scroll :advanced? advanced?
:symbol symbol :network network}]))
(defview send-transaction-modal [] (defview send-transaction-modal []
(letsubs [transaction [:wallet.send/unsigned-transaction] (letsubs [transaction [:wallet.send/unsigned-transaction]
symbol [:wallet.send/symbol] symbol [:wallet.send/symbol]
advanced? [:wallet.send/advanced?] advanced? [:wallet.send/advanced?]
network [:get-current-account-network]
scroll (atom nil)] scroll (atom nil)]
(if transaction (if transaction
[send-transaction-panel {:modal? true :transaction transaction :scroll scroll :advanced? advanced? :symbol symbol}] [send-transaction-panel {:modal? true :transaction transaction :scroll scroll :advanced? advanced?
symbol symbol :network network}]
[react/view wallet.styles/wallet-modal-container [react/view wallet.styles/wallet-modal-container
[react/view components.styles/flex [react/view components.styles/flex
[status-bar/status-bar {:type :modal-wallet}] [status-bar/status-bar {:type :modal-wallet}]

View File

@ -10,7 +10,9 @@
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.screens.wallet.transactions.styles :as styles] [status-im.ui.screens.wallet.transactions.styles :as styles]
[status-im.utils.money :as money])) [status-im.utils.money :as money]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.ethereum.core :as ethereum]))
(defn history-action [filter?] (defn history-action [filter?]
(cond-> (cond->
@ -44,12 +46,13 @@
(:postponed :pending) (transaction-icon :icons/arrow-right components.styles/color-gray4-transparent components.styles/color-gray7) (:postponed :pending) (transaction-icon :icons/arrow-right components.styles/color-gray4-transparent components.styles/color-gray7)
(throw (str "Unknown transaction type: " k)))) (throw (str "Unknown transaction type: " k))))
(defn render-transaction [{:keys [hash from-contact to-contact to from type value time-formatted symbol] :as transaction}] (defn render-transaction [{:keys [hash from-contact to-contact to from type value time-formatted symbol] :as transaction} network]
(let [[label contact address (let [[label contact address
contact-accessibility-label contact-accessibility-label
address-accessibility-label] (if (inbound? type) address-accessibility-label] (if (inbound? type)
[(i18n/label :t/from) from-contact from :sender-text :sender-address-text] [(i18n/label :t/from) from-contact from :sender-text :sender-address-text]
[(i18n/label :t/to) to-contact to :recipient-name-text :recipient-address-text])] [(i18n/label :t/to) to-contact to :recipient-name-text :recipient-address-text])
{:keys [decimals]} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[list/touchable-item #(re-frame/dispatch [:show-transaction-details hash]) [list/touchable-item #(re-frame/dispatch [:show-transaction-details hash])
[react/view {:accessibility-label :transaction-item} [react/view {:accessibility-label :transaction-item}
[list/item [list/item
@ -60,7 +63,7 @@
:ellipsize-mode "tail" :ellipsize-mode "tail"
:number-of-lines 1} :number-of-lines 1}
[react/text {:accessibility-label :amount-text} [react/text {:accessibility-label :amount-text}
(->> value (money/wei-> :eth) money/to-fixed str)] (-> value (money/internal->formatted symbol decimals) money/to-fixed str)]
" " " "
[react/text {:accessibility-label :currency-text} [react/text {:accessibility-label :currency-text}
(clojure.string/upper-case (name symbol))]] (clojure.string/upper-case (name symbol))]]
@ -91,11 +94,12 @@
(defview history-list [] (defview history-list []
(letsubs [transactions-history-list [:wallet.transactions/transactions-history-list] (letsubs [transactions-history-list [:wallet.transactions/transactions-history-list]
filter-data [:wallet.transactions/filters]] filter-data [:wallet.transactions/filters]
network [:get-current-account-network]]
[react/view components.styles/flex [react/view components.styles/flex
[list/section-list {:sections (map #(update-transactions % filter-data) transactions-history-list) [list/section-list {:sections (map #(update-transactions % filter-data) transactions-history-list)
:key-fn :hash :key-fn :hash
:render-fn render-transaction :render-fn #(render-transaction % network)
:empty-component [react/text {:style styles/empty-text} :empty-component [react/text {:style styles/empty-text}
(i18n/label :t/transactions-history-empty)] (i18n/label :t/transactions-history-empty)]
:on-refresh #(re-frame/dispatch [:update-transactions]) :on-refresh #(re-frame/dispatch [:update-transactions])

View File

@ -380,30 +380,26 @@
:symbol :STT :symbol :STT
:decimals 18 :decimals 18
:address "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"} :address "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"}
{:name "Aragon Test Token"
:symbol :ATT
:decimals 1
:address "0x00a8e52df8f4f1f4b67bded9ae6090b35489a973"}
{:name "Handy Test Token" {:name "Handy Test Token"
:symbol :HND :symbol :HND
:decimals 18 :decimals 0
:address "0x9e47fb3049f0d9c953f5428ce2e6c3a8321780bf"} :address "0xdee43a267e8726efd60c2e7d5b81552dcd4fa35c"}
{:name "Lucky XS Test" {:name "Lucky XS Test"
:symbol :LXS :symbol :LXS
:decimals 18 :decimals 2
:address "0xf29d2dc0687d7d49f57d4a731ac8bfb6edc23473"} :address "0x703d7dc0bc8e314d65436adf985dda51e09ad43b"}
{:name "Adi Test Token" {:name "Adi Test Token"
:symbol :ADI :symbol :ADI
:decimals 18 :decimals 7
:address "0xd2a816110c1177478c7e644ae4853d8e80aaec35"} :address "0xe639e24346d646e927f323558e6e0031bfc93581"}
{:name "Wagner Test Token" {:name "Wagner Test Token"
:symbol :WGN :symbol :WGN
:decimals 18 :decimals 10
:address "0x65c69bc258afa0906683f42e576b272a95c203dd"} :address "0x2e7cd05f437eb256f363417fd8f920e2efa77540"}
{:name "Modest Test Token" {:name "Modest Test Token"
:symbol :MDS :symbol :MDS
:decimals 18 :decimals 18
:address "0x972b0570d9cd8b7c41aa8349f707ec7356daa825"}])}) :address "0x57cc9b83730e6d22b224e9dc3e370967b44a2de0"}])})
(defn tokens-for [chain] (defn tokens-for [chain]
(get all chain)) (get all chain))

View File

@ -90,6 +90,32 @@
(when-let [bn (bignumber n)] (when-let [bn (bignumber n)]
(.dividedBy bn (bignumber (from-decimal decimals))))) (.dividedBy bn (bignumber (from-decimal decimals)))))
(defn unit->token [n decimals]
(when-let [bn (bignumber n)]
(.times bn (bignumber (from-decimal decimals)))))
;;NOTE(goranjovic) - We have two basic representations of values that refer to cryptocurrency amounts: formatted and
;; internal. Formatted representation is the one we show on screens and include in reports, whereas internal
;; representation is the one that we pass on to ethereum network for execution, transfer, etc.
;; The difference between the two depends on the number of decimals, i.e. internal representation is expressed in terms
;; of a whole number of smallest divisible parts of the formatted value.
;;
;; 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 decimals parameter
;; to get the amount scale right.
(defn formatted->internal [n symbol decimals]
(if (= :ETH symbol)
(ether->wei n)
(unit->token n decimals)))
(defn internal->formatted [n symbol decimals]
(if (= :ETH symbol)
(wei->ether n)
(token->unit n decimals)))
(defn fee-value [gas gas-price] (defn fee-value [gas gas-price]
(.times (bignumber gas) (bignumber gas-price))) (.times (bignumber gas) (bignumber gas-price)))