test: wallet-connect transaction utils

This commit is contained in:
Cristian Lungu 2024-09-17 18:18:14 +03:00
parent 699986c0b2
commit 3c00b7fdae
No known key found for this signature in database
GPG Key ID: 00D675EDE1B264D9
3 changed files with 295 additions and 24 deletions

View File

@ -3,13 +3,32 @@
[clojure.string :as string]
[native-module.core :as native-module]
[promesa.core :as promesa]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.hex :as hex]
[utils.money :as money]
[utils.transforms :as transforms]))
(def ^:private ?string-or-number
[:or number? string?])
(def ?transaction
[:map
[:to :string]
[:from :string]
[:value ?string-or-number]
[:gas {:optional true} ?string-or-number]
[:gasPrice {:optional true} ?string-or-number]
[:gasLimit {:optional true} ?string-or-number]
[:nonce {:optional true} ?string-or-number]
[:maxFeePerGas {:optional true} ?string-or-number]
[:maxPriorityFeePerGas {:optional true} ?string-or-number]
[:input {:optional true} [:maybe :string]]
[:data {:optional true} [:maybe :string]]])
(defn transaction-request?
[event]
(->> (data-store/get-request-method event)
@ -20,7 +39,7 @@
;; show the estimated time, but when we implement it, we should allow to change it
(def ^:constant default-tx-priority :medium)
(defn- strip-hex-prefix
(defn strip-hex-prefix
"Strips the extra 0 in hex value if present"
[hex-value]
(let [formatted-hex (string/replace hex-value #"^0x0*" "0x")]
@ -28,8 +47,8 @@
"0x0"
formatted-hex)))
(defn- format-tx-hex-values
"Due to how status-go expects hex values, we should remove the extra 0s in transaction hex values e.g. 0x0f -> 0xf"
(defn format-tx-hex-values
"Apply f on transaction keys that are hex numbers"
[tx f]
(let [tx-keys [:gasLimit :gas :gasPrice :nonce :value :maxFeePerGas :maxPriorityFeePerGas]]
(reduce (fn [acc tx-key]
@ -40,7 +59,14 @@
tx
tx-keys)))
(defn- prepare-transaction-for-rpc
(schema/=> format-tx-hex-values
[:=>
[:catn
[:tx ?transaction]
[:f fn?]]
?transaction])
(defn prepare-transaction-for-rpc
"Formats the transaction and transforms it into a stringified JS object, ready to be passed to an RPC call."
[tx]
(-> tx
@ -50,42 +76,67 @@
bean/->js
(transforms/js-stringify 0)))
(schema/=> prepare-transaction-for-rpc
[:=>
[:cat ?transaction]
:string])
(defn beautify-transaction
[tx]
(let [hex->number #(-> % (subs 2) native-module/hex-to-number)]
(-> tx
(format-tx-hex-values hex->number)
clj->js
(js/JSON.stringify nil 2))))
(-> tx
(format-tx-hex-values hex/hex-to-number)
clj->js
(js/JSON.stringify nil 2)))
(defn- gwei->hex
(schema/=> beautify-transaction
[:=>
[:cat ?transaction]
:string])
(defn gwei->hex
[gwei]
(->> gwei
money/gwei->wei
native-module/number-to-hex
(str "0x")))
(schema/=> gwei->hex
[:=>
[:cat ?string-or-number]
:string])
(defn- get-max-fee-per-gas-key
"Mapping transaction priority (which determines how quickly a tx is processed)
to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas`.
Returns `:high` | `:medium` | `:low`"
to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas` "
[tx-priority]
(get {:high :maxFeePerGasHigh
:medium :maxFeePerGasMedium
:low :maxFeePerGasLow}
tx-priority))
(defn- dynamic-fee-tx?
(def ?tx-priority [:enum :high :medium :low])
(def ?max-fee-priority [:enum :maxFeePerGasHigh :maxFeePerGasMedium :maxFeePerGasLow])
(schema/=> get-max-fee-per-gas-key
[:=>
[:cat ?tx-priority]
?max-fee-priority])
(defn dynamic-fee-tx?
"Checks if a transaction has dynamic fees (EIP1559)"
[tx]
(every? tx [:maxFeePerGas :maxPriorityFeePerGas]))
(schema/=> dynamic-fee-tx?
[:=>
[:cat ?transaction]
:boolean])
(defn- tx->eip1559-tx
"Adds `:maxFeePerGas` and `:maxPriorityFeePerGas` for dynamic fee support (EIP1559) and
removes `:gasPrice`, if the chain supports EIP1559 and the transaction doesn't already
have dynamic fees."
[tx suggested-fees tx-priority]
[tx tx-priority suggested-fees]
(if (and (:eip1559Enabled suggested-fees)
(not (dynamic-fee-tx? tx)))
(let [max-fee-per-gas-key (get-max-fee-per-gas-key tx-priority)
@ -99,17 +150,49 @@
(dissoc :gasPrice)))
tx))
(defn- prepare-transaction-fees
(def ?suggested-fees
[:map
[:eip1559Enabled boolean?]
[:maxFeePerGasLow number?]
[:maxFeePerGasMedium number?]
[:maxFeePerGasHigh number?]
[:maxPriorityFeePerGas number?]
[:gasPrice {:optional true} number?]
[:baseFee {:optional true} number?]
[:l1GasFee {:optional true} number?]])
(schema/=> tx->eip1559-tx
[:=>
[:catn
[:tx ?transaction]
[:tx-priority ?tx-priority]
[:suggested-fees ?suggested-fees]]
?transaction])
(defn rename-gas-limit
[tx]
(if (:gasLimit tx)
(-> tx
;; NOTE: `gasLimit` is ignored on status-go when building a transaction
;; (`wallet_buildTransaction`), so we're setting it as the `gas` property
(assoc :gas (:gasLimit tx))
(dissoc :gasLimit))
tx))
(defn prepare-transaction-fees
"Makes sure the transaction has the correct gas and fees properties"
[tx tx-priority suggested-fees]
(-> (assoc tx
;; NOTE: `gasLimit` is ignored on status-go when building a transaction
;; (`wallet_buildTransaction`), so we're setting it as the `gas` property
:gas
(or (:gasLimit tx)
(:gas tx)))
(dissoc :gasLimit)
(tx->eip1559-tx suggested-fees tx-priority)))
(-> tx
rename-gas-limit
(tx->eip1559-tx tx-priority suggested-fees)))
(schema/=> prepare-transaction-fees
[:=>
[:catn
[:tx ?transaction]
[:tx-priority ?tx-priority]
[:suggested-fees ?suggested-fees]]
?transaction])
(defn prepare-transaction
"Formats and builds the incoming transaction, adding the missing properties and returning the final
@ -126,6 +209,17 @@
:tx-hash message-to-sign
:suggested-fees suggested-fees}))
(schema/=> prepare-transaction
[:=>
[:catn
[:tx ?transaction
:chain-id :int
:tx-priority ?tx-priority]]
[:map {:closed true}
[:tx-args :string]
[:tx-hash :string]
[:suggested-fees ?suggested-fees]]])
(defn sign-transaction
[password address tx-hash tx-args chain-id]
(promesa/let
@ -133,6 +227,16 @@
raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)]
raw-tx))
(schema/=> sign-transaction
[:=>
[:catn
[:password :string]
[:address :string]
[:tx-hash :string]
[:tx-args ?transaction]
[:chain-id :int]]
:string])
(defn send-transaction
[password address tx-hash tx-args chain-id]
(promesa/let
@ -141,3 +245,13 @@
tx-args
signature)]
tx))
(schema/=> sign-transaction
[:=>
[:catn
[:password :string]
[:address :string]
[:tx-hash :string]
[:tx-args ?transaction]
[:chain-id :int]]
:string])

View File

@ -0,0 +1,148 @@
(ns status-im.contexts.wallet.wallet-connect.utils.transactions-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as sut]
[utils.transforms :as transforms]))
(deftest strip-hex-prefix-test
(testing "passed value with extra 0 hex prefix"
(is (= (sut/strip-hex-prefix "0x0123")
"0x123")))
(testing "passed empty 0x value"
(is (= (sut/strip-hex-prefix "0x")
"0x0")))
(testing "passed value without 0 in prefix"
(is (= (sut/strip-hex-prefix "0x123")
"0x123"))))
(deftest format-tx-hex-values-test
(testing "applies f only on the tx keys that are hex numbers"
(let [tx {:to "0x0123"
:from "0x0456"
:value "0x0fff"
:gas "0x01"}
expected-tx {:to "0x0123"
:from "0x0456"
:value "0xfff"
:gas "0x1"}]
(is (= (sut/format-tx-hex-values tx sut/strip-hex-prefix)
expected-tx)))))
(cljs.test/test-var #'format-tx-hex-values-test)
(deftest prepare-transaction-for-rpc-test
(testing "original transaction nonce is removed"
(let [tx {:to "0x123"
:from "0x456"
:value "0xfff"
:nonce "0x1"}]
(is (-> (sut/prepare-transaction-for-rpc tx)
(transforms/json->clj)
:nonce
nil?))))
(testing "transaction hex numbers are formatted"
(let [tx {:to "0x123"
:from "0x456"
:value "0x0fff"
:nonce "0x1"}]
(is (-> (sut/prepare-transaction-for-rpc tx)
(transforms/json->clj)
:value
(= "0xfff"))))))
(deftest beautify-transaction-test
(testing "hex number values are converted to utf-8"
(let [tx {:to "0x123"
:from "0x456"
:value "0xfff"}]
(is (-> (sut/beautify-transaction tx)
(transforms/json->clj)
:value
(= 4095))))))
(cljs.test/test-var #'beautify-transaction-test)
(deftest gwei->hex-test
(testing "gwei amount is converted to wei as hex"
(is (-> (sut/gwei->hex "1000000000")
(= "0xde0b6b3a7640000")))))
(deftest dynamic-fee-tx?-test
(testing "correctly asserts tx as dynamic"
(is (-> {:to "0x123"
:from "0x123"
:value "0x123"
:maxFeePerGas "0x123"
:maxPriorityFeePerGas "0x1"}
sut/dynamic-fee-tx?)))
(testing "correnctly asserts tx as not dynamic (legacy)"
(is (-> {:to "0x123"
:from "0x123"
:value "0x123"}
sut/dynamic-fee-tx?
not)))
(testing "asserts tx as not dynamic (legacy) when required keys are only partially present"
(is (-> {:to "0x123"
:from "0x123"
:value "0x123"
:maxFeePerGas "0x123"}
sut/dynamic-fee-tx?
not))))
(deftest prepare-transaction-fees-test
(let [suggested-fees {:eip1559Enabled true
:maxFeePerGasLow 1
:maxFeePerGasMedium 2
:maxFeePerGasHigh 3
:maxPriorityFeePerGas 100}]
(testing "correctly prepares eip1559 gas values"
(let [tx {:to "0x123"
:from "0x123"
:value "0x123"}
tx-priority :high]
(is (= (sut/prepare-transaction-fees tx
tx-priority
suggested-fees)
{:to (:to tx)
:from (:from tx)
:value (:value tx)
:maxFeePerGas "0xb2d05e00"
:maxPriorityFeePerGas "0x174876e800"}))))
(testing "renames gasLimit key to gas and removed gasPrice"
(let [gasLimit "0x1234"
prepared-tx (sut/prepare-transaction-fees {:to "0x123"
:from "0x123"
:value "0x123"
:gasPrice "0x123"
:gasLimit gasLimit}
:high
suggested-fees)]
(is (and (= (:gas prepared-tx) gasLimit)
(nil? (:gasLimit prepared-tx))
(nil? (:gasPrice prepared-tx))))))
(testing "returns original tx for non-dynamic transactions"
(let [tx {:to "0x123"
:from "0x123"
:value "0x123"
:gasPrice "0x123"}
suggested-fees (assoc suggested-fees :eip1559Enabled false)]
(is (= (sut/prepare-transaction-fees tx :high suggested-fees)
tx))))
(testing "throws a schema exception due wrong arguments"
(let [tx {:to "0x123"
:from "0x123"
:value "0x123"}
wrong-tx-priority :wrong
wrong-suggested-fees {}]
(is (thrown? js/Error
(sut/prepare-transaction-fees tx
wrong-tx-priority
wrong-suggested-fees)))))))

View File

@ -37,3 +37,12 @@
[:=>
[:cat [:or :string :int]]
:string])
(defn hex-to-number
[hex]
(-> hex normalize-hex native-module/hex-to-number))
(schema/=> hex-to-number
[:=>
[:cat :string]
:int])