Check minimum amount

Use string for inputs

validate gas is not 0

Handle signed/unsigned transactions
This commit is contained in:
Andrea Maria Piana 2018-06-14 09:40:00 +02:00 committed by Roman Volosovskyi
parent 8848b37433
commit 5a08383bde
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
8 changed files with 170 additions and 32 deletions

View File

@ -0,0 +1,53 @@
(ns status-im.models.wallet
(:require [status-im.utils.money :as money]))
(def min-gas-price-wei (money/bignumber 1))
(defmulti invalid-send-parameter? (fn [type _] type))
(defmethod invalid-send-parameter? :gas-price [_ value]
(cond
(not value) :invalid-number
(< (money/->wei :gwei value) min-gas-price-wei) :not-enough-wei))
(defmethod invalid-send-parameter? :default [_ value]
(when (or (not value)
(<= value 0))
:invalid-number))
(defn- calculate-max-fee
[gas gas-price]
(if (and gas gas-price)
(money/to-fixed (money/wei->ether (.times gas gas-price)))
"0"))
(defn- edit-max-fee [edit]
(let [gas (get-in edit [:gas-price :value-number])
gas-price (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gas-price))))
(defn add-max-fee [{:keys [gas gas-price] :as transaction}]
(assoc transaction :max-fee (calculate-max-fee gas gas-price)))
(defn build-edit [edit-value key value]
"Takes the previous edit, either :gas or :gas-price and a value as string.
Wei for gas, and gwei for gas price.
Validates them and sets max fee"
(let [bn-value (money/bignumber value)
invalid? (invalid-send-parameter? key bn-value)
data (if invalid?
{:value value
:max-fee 0
:invalid? invalid?}
{:value value
:value-number (if (= :gas-price key)
(money/->wei :gwei bn-value)
bn-value)
:invalid? false})]
(-> edit-value
(assoc key data)
edit-max-fee)))
(defn edit-value
[key value {:keys [db]}]
{:db (update-in db [:wallet :edit] build-edit key value)})

View File

@ -454,6 +454,7 @@
:share "Share" :share "Share"
:eth "ETH" :eth "ETH"
:gwei "Gwei" :gwei "Gwei"
:wallet-send-min-wei "Min 1 wei"
:currency "Currency" :currency "Currency"
:usd-currency "USD" :usd-currency "USD"
:currency-display-name-aed "Emirati Dirham" :currency-display-name-aed "Emirati Dirham"

View File

@ -8,6 +8,7 @@
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.prices :as prices] [status-im.utils.prices :as prices]
[status-im.utils.transactions :as transactions] [status-im.utils.transactions :as transactions]
[status-im.models.wallet :as models.wallet]
[taoensso.timbre :as log] [taoensso.timbre :as log]
status-im.ui.screens.wallet.request.events status-im.ui.screens.wallet.request.events
[status-im.utils.money :as money] [status-im.utils.money :as money]
@ -248,7 +249,11 @@
:wallet/update-gas-price-success :wallet/update-gas-price-success
(fn [db [_ price edit?]] (fn [db [_ price edit?]]
(if edit? (if edit?
(assoc-in db [:wallet :edit :gas-price] {:value price :invalid? false}) (:db (models.wallet/edit-value
:gas-price
(money/to-fixed
(money/wei-> :gwei price))
{:db db}))
(assoc-in db [:wallet :send-transaction :gas-price] price)))) (assoc-in db [:wallet :send-transaction :gas-price] price))))
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -14,6 +14,7 @@
[status-im.utils.security :as security] [status-im.utils.security :as security]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.models.wallet :as models.wallet]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.transport.utils :as transport.utils] [status-im.transport.utils :as transport.utils]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
@ -378,13 +379,8 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.send/edit-value :wallet.send/edit-value
(fn [{:keys [db]} [_ key value]] (fn [cofx [_ key value]]
(let [bn-value (money/bignumber value) (models.wallet/edit-value key value cofx)))
data (if bn-value
{:value bn-value
:invalid? false}
{:invalid? true})]
{:db (update-in db [:wallet :edit key] merge data)})))
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.send/set-gas-details :wallet.send/set-gas-details
@ -400,11 +396,15 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.send/reset-gas-default :wallet.send/reset-gas-default
(fn [{:keys [db]}] (fn [{:keys [db] :as cofx}]
{:dispatch [:wallet/update-gas-price true] (let [gas-estimate (money/to-fixed
:db (assoc-in db [:wallet :edit :gas] (ethereum/estimate-gas
{:value (ethereum/estimate-gas (-> db :wallet :send-transaction :symbol)) (-> db :wallet :send-transaction :symbol)))]
:invalid? false})})) (assoc (models.wallet/edit-value
:gas
gas-estimate
cofx)
:dispatch [:wallet/update-gas-price true]))))
(handlers/register-handler-fx (handlers/register-handler-fx
:close-transaction-sent-screen :close-transaction-sent-screen

View File

@ -1,6 +1,7 @@
(ns status-im.ui.screens.wallet.send.subs (ns status-im.ui.screens.wallet.send.subs
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.models.wallet :as models.wallet]
[status-im.utils.hex :as utils.hex])) [status-im.utils.hex :as utils.hex]))
(re-frame/reg-sub ::send-transaction (re-frame/reg-sub ::send-transaction
@ -54,9 +55,30 @@
{:gas (or (:gas send-transaction) (:gas unsigned-transaction)) {:gas (or (:gas send-transaction) (:gas unsigned-transaction))
:gas-price (or (:gas-price send-transaction) (:gas-price unsigned-transaction))})))) :gas-price (or (:gas-price send-transaction) (:gas-price unsigned-transaction))}))))
(defn edit-or-transaction-data
"Set up edit data structure, defaulting to transaction when not available"
[transaction edit]
(cond-> edit
(not (get-in edit [:gas-price :value]))
(models.wallet/build-edit
:gas-price
(money/to-fixed (money/wei-> :gwei (:gas-price transaction))))
(not (get-in edit [:gas :value]))
(models.wallet/build-edit
:gas
(money/to-fixed (:gas transaction)))))
(re-frame/reg-sub :wallet/edit (re-frame/reg-sub :wallet/edit
:<- [::send-transaction]
:<- [::unsigned-transaction]
:<- [:wallet] :<- [:wallet]
:edit) (fn [[send-transaction unsigned-transaction {:keys [edit]}]]
(edit-or-transaction-data
(if (:id send-transaction)
unsigned-transaction
send-transaction)
edit)))
(defn sign-enabled? [amount-error to amount] (defn sign-enabled? [amount-error to amount]
(and (and
@ -68,8 +90,11 @@
:<- [::send-transaction] :<- [::send-transaction]
:<- [:balance] :<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]] (fn [[{:keys [amount symbol] :as transaction} balance]]
(assoc transaction :sufficient-funds? (or (nil? amount) (-> transaction
(money/sufficient-funds? amount (get balance symbol)))))) (models.wallet/add-max-fee)
(assoc :sufficient-funds?
(or (nil? amount)
(money/sufficient-funds? amount (get balance symbol)))))))
(re-frame/reg-sub :wallet.send/unsigned-transaction (re-frame/reg-sub :wallet.send/unsigned-transaction
:<- [::unsigned-transaction] :<- [::unsigned-transaction]
@ -79,7 +104,7 @@
(when transaction (when transaction
(let [contact (contacts (utils.hex/normalize-hex to)) (let [contact (contacts (utils.hex/normalize-hex to))
sufficient-funds? (money/sufficient-funds? value (get balance symbol))] sufficient-funds? (money/sufficient-funds? value (get balance symbol))]
(cond-> (assoc transaction (cond-> (assoc (models.wallet/add-max-fee transaction)
:amount value :amount value
:sufficient-funds? sufficient-funds?) :sufficient-funds? sufficient-funds?)
contact (assoc :to-name (:name contact))))))) contact (assoc :to-name (:name contact)))))))

View File

@ -92,10 +92,6 @@
(i18n/label :t/transactions-sign-transaction) (i18n/label :t/transactions-sign-transaction)
[vector-icons/icon :icons/forward {:color (if immediate-sign-enabled? :white :gray)}]]])) [vector-icons/icon :icons/forward {:color (if immediate-sign-enabled? :white :gray)}]]]))
(defn- max-fee [gas gas-price]
(when (and gas gas-price)
(money/wei->ether (.times gas gas-price))))
(defn return-to-transaction [dapp-transaction?] (defn return-to-transaction [dapp-transaction?]
(if dapp-transaction? (if dapp-transaction?
(re-frame/dispatch [:navigate-to-modal :wallet-send-transaction-modal]) (re-frame/dispatch [:navigate-to-modal :wallet-send-transaction-modal])
@ -116,12 +112,13 @@
unsigned-transaction [:wallet.send/unsigned-transaction] unsigned-transaction [:wallet.send/unsigned-transaction]
network [:get-current-account-network] network [:get-current-account-network]
{gas-edit :gas {gas-edit :gas
max-fee :max-fee
gas-price-edit :gas-price} [:wallet/edit]] gas-price-edit :gas-price} [:wallet/edit]]
(let [modal? (:id send-transaction) (let [modal? (:id send-transaction)
;;TODO(goranjovic) - unify unsigned and regular transaction subs ;;TODO(goranjovic) - unify unsigned and regular transaction subs
{:keys [amount symbol] :as transaction} (if modal? unsigned-transaction send-transaction) {:keys [amount symbol] :as transaction} (if modal? unsigned-transaction send-transaction)
gas (or (:value gas-edit) (:gas transaction)) gas (:value gas-edit)
gas-price (or (:value gas-price-edit) (:gas-price transaction)) gas-price (:value gas-price-edit)
{:keys [decimals]} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)] {:keys [decimals]} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[wallet.components/simple-screen {:status-bar-type :modal-wallet} [wallet.components/simple-screen {:status-bar-type :modal-wallet}
[toolbar false modal? act/close-white [toolbar false modal? act/close-white
@ -135,7 +132,7 @@
[react/view styles/gas-input-wrapper [react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input [react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas %]) {:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas %])
:default-value (str (money/to-fixed gas)) :default-value gas
:accessibility-label :gas-limit-input})]]] :accessibility-label :gas-limit-input})]]]
(when (:invalid? gas-edit) (when (:invalid? gas-edit)
[tooltip/tooltip (i18n/label :t/invalid-number)])] [tooltip/tooltip (i18n/label :t/invalid-number)])]
@ -145,13 +142,15 @@
(i18n/label :t/gas-price) (i18n/label :t/gas-price)
[react/view styles/gas-input-wrapper [react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input [react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas-price (money/->wei :gwei %)]) {:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas-price %])
:default-value (str (money/to-fixed (money/wei-> :gwei gas-price))) :default-value gas-price
:accessibility-label :gas-price-input})] :accessibility-label :gas-price-input})]
[wallet.components/cartouche-secondary-text [wallet.components/cartouche-secondary-text
(i18n/label :t/gwei)]]] (i18n/label :t/gwei)]]]
(when (:invalid? gas-price-edit) (when (:invalid? gas-price-edit)
[tooltip/tooltip (i18n/label :t/invalid-number)])]] [tooltip/tooltip (i18n/label (if (= :invalid-number (:invalid? gas-price-edit))
:t/invalid-number
:t/wallet-send-min-wei))])]]
[react/view styles/transaction-fee-info [react/view styles/transaction-fee-info
[react/view styles/transaction-fee-info-icon [react/view styles/transaction-fee-info-icon
@ -171,21 +170,22 @@
(i18n/label :t/wallet-transaction-total-fee) (i18n/label :t/wallet-transaction-total-fee)
[react/view {:accessibility-label :total-fee-input} [react/view {:accessibility-label :total-fee-input}
[wallet.components/cartouche-text-content [wallet.components/cartouche-text-content
(str (money/to-fixed (max-fee gas gas-price))) (str max-fee " " (i18n/label :t/eth))]]]]
(i18n/label :t/eth)]]]]
[bottom-buttons/bottom-buttons styles/fee-buttons [bottom-buttons/bottom-buttons styles/fee-buttons
[button/button {:on-press #(re-frame/dispatch [:wallet.send/reset-gas-default]) [button/button {:on-press #(re-frame/dispatch [:wallet.send/reset-gas-default])
:accessibility-label :reset-to-default-button} :accessibility-label :reset-to-default-button}
(i18n/label :t/reset-default)] (i18n/label :t/reset-default)]
[button/button {:on-press #(do (re-frame/dispatch [:wallet.send/set-gas-details gas gas-price]) [button/button {:on-press #(do (re-frame/dispatch [:wallet.send/set-gas-details
(:value-number gas-edit)
(:value-number gas-price-edit)])
(return-to-transaction modal?)) (return-to-transaction modal?))
:accessibility-label :done-button :accessibility-label :done-button
:disabled? (or (:invalid? gas-edit) :disabled? (or (:invalid? gas-edit)
(:invalid? gas-price-edit))} (:invalid? gas-price-edit))}
(i18n/label :t/done)]]]]))) (i18n/label :t/done)]]]])))
(defn- advanced-cartouche [{:keys [gas gas-price]} modal?] (defn- advanced-cartouche [{:keys [max-fee gas gas-price]} modal?]
[react/view [react/view
[wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas]) [wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas])
(re-frame/dispatch [:navigate-to-modal :wallet-transaction-fee]))} (re-frame/dispatch [:navigate-to-modal :wallet-transaction-fee]))}
@ -193,7 +193,7 @@
[react/view {:style styles/advanced-options-text-wrapper [react/view {:style styles/advanced-options-text-wrapper
:accessibility-label :transaction-fee-button} :accessibility-label :transaction-fee-button}
[react/text {:style styles/advanced-fees-text} [react/text {:style styles/advanced-fees-text}
(str (money/to-fixed (max-fee gas gas-price)) " " (i18n/label :t/eth))] (str max-fee " " (i18n/label :t/eth))]
[react/text {:style styles/advanced-fees-details-text} [react/text {:style styles/advanced-fees-details-text}
(str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]]) (str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]])

View File

@ -0,0 +1,52 @@
(ns status-im.test.models.wallet
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.money :as money]
[status-im.models.wallet :as model]))
(deftest valid-min-gas-price-test
(testing "not an number"
(is (= :invalid-number (model/invalid-send-parameter? :gas-price nil))))
(testing "a number less than the minimum"
(is (= :not-enough-wei (model/invalid-send-parameter? :gas-price (money/bignumber "0.0000000001")))))
(testing "a number greater than the mininum"
(is (not (model/invalid-send-parameter? :gas-price 3))))
(testing "the minimum"
(is (not (model/invalid-send-parameter? :gas-price (money/bignumber "0.000000001"))))))
(deftest valid-gas
(testing "not an number"
(is (= :invalid-number (model/invalid-send-parameter? :gas nil))))
(testing "0"
(is (= :invalid-number (model/invalid-send-parameter? :gas 0))))
(testing "a number"
(is (not (model/invalid-send-parameter? :gas 1)))))
(deftest build-edit-test
(testing "an invalid edit"
(let [actual (-> {}
(model/build-edit :gas "invalid")
(model/build-edit :gas-price "0.00000000001"))]
(testing "it marks gas-price as invalid"
(is (get-in actual [:gas-price :invalid?])))
(testing "it does not change value"
(is (= "0.00000000001" (get-in actual [:gas-price :value]))))
(testing "it marks gas as invalid"
(is (get-in actual [:gas :invalid?])))
(testing "it does not change gas value"
(is (= "invalid" (get-in actual [:gas :value]))))
(testing "it sets max-fee to 0"
(is (= "0" (:max-fee actual))))))
(testing "an valid edit"
(let [actual (-> {}
(model/build-edit :gas "21000")
(model/build-edit :gas-price "10"))]
(testing "it does not mark gas-price as invalid"
(is (not (get-in actual [:gas-price :invalid?]))))
(testing "it sets the value in number for gas-price, in gwei"
(is (= "10000000000" (str (get-in actual [:gas-price :value-number])))))
(testing "it does not mark gas as invalid"
(is (not (get-in actual [:gas :invalid?]))))
(testing "it sets the value in number for gasi"
(is (= "21000" (str (get-in actual [:gas :value-number])))))
(testing "it calculates max-fee"
(is (= "0.00021" (:max-fee actual)))))))

View File

@ -15,6 +15,7 @@
[status-im.test.models.account] [status-im.test.models.account]
[status-im.test.models.contact] [status-im.test.models.contact]
[status-im.test.models.network] [status-im.test.models.network]
[status-im.test.models.wallet]
[status-im.test.transport.core] [status-im.test.transport.core]
[status-im.test.transport.inbox] [status-im.test.transport.inbox]
[status-im.test.transport.handlers] [status-im.test.transport.handlers]
@ -65,6 +66,7 @@
'status-im.test.models.account 'status-im.test.models.account
'status-im.test.models.contact 'status-im.test.models.contact
'status-im.test.models.network 'status-im.test.models.network
'status-im.test.models.wallet
'status-im.test.bots.events 'status-im.test.bots.events
'status-im.test.wallet.subs 'status-im.test.wallet.subs
'status-im.test.wallet.transactions.subs 'status-im.test.wallet.transactions.subs