Skip to content

Commit

Permalink
[extensions] ethereum call decode params
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Shovkoplyas <[email protected]>
  • Loading branch information
flexsurfer committed Oct 25, 2018
1 parent 3eda5e3 commit f8ef431
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/status_im/extensions/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
:arguments {:to :string
:method :string
:params? :vector
:outputs? :vector
:on-result? :event}}}
:hooks {:commands commands/command-hook}})

Expand Down
13 changes: 9 additions & 4 deletions src/status_im/extensions/ethereum.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[status-im.models.wallet :as models.wallet]
[status-im.utils.ethereum.abi-spec :as abi-spec]
[status-im.utils.fx :as fx]
[status-im.ui.screens.navigation :as navigation]))
[status-im.ui.screens.navigation :as navigation]
[clojure.string :as string]))

(handlers/register-handler-fx
:extensions/transaction-on-result
Expand All @@ -29,11 +30,15 @@

(handlers/register-handler-fx
:extensions/ethereum-call
(fn [_ [_ {:keys [to method params on-result]}]]
(fn [_ [_ {:keys [to method params outputs on-result]}]]
(let [tx-object {:to to :data (when method (abi-spec/encode method params))}]
{:browser/call-rpc [{"jsonrpc" "2.0"
"method" "eth_call"
"params" [tx-object "latest"]}
#(when on-result
(re-frame/dispatch (on-result {:error %1 :result (when %2
(get (js->clj %2) "result"))})))]})))
(let [result-str (when %2
(get (js->clj %2) "result"))
result (if (and outputs result-str)
(abi-spec/decode (string/replace result-str #"0x" "") outputs)
result-str)]
(re-frame/dispatch (on-result {:error %1 :result result}))))]})))
122 changes: 121 additions & 1 deletion src/status_im/utils/ethereum/abi_spec.cljs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
(ns status-im.utils.ethereum.abi-spec
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.js-dependencies :as dependencies]))
[status-im.js-dependencies :as dependencies]
[clojure.string :as str]))

;; Utility functions for encoding

Expand Down Expand Up @@ -33,6 +34,12 @@
(defn number-to-hex [x]
(subs (.numberToHex utils x) 2))

(defn hex-to-utf8 [x]
(.hexToUtf8 utils (str "0x" x)))

(defn hex-to-number [x]
(.hexToNumber utils (str "0x" x)))

(defn sha3 [s]
(.sha3 utils (str s)))

Expand Down Expand Up @@ -267,3 +274,116 @@
params)]
(str method-id (enc {:type :tuple
:value params})))))

;; ======= decode

(defn substr [val s l]
(subs val s (+ s l)))

;; "[]" -> 0 , "[1]" -> 1
(defn arr-size [val]
(int (apply str (rest (butlast val)))))

;; [2] -> 2 , [1] -> 1 , [] - > 1
(defn nested-size [val]
(let [num (arr-size val)]
(if (zero? num) 1 num)))

;; '("[1]" "[]") or nil
(defn list-of-nested-types [type]
(when-let [res (re-seq #"(\[[0-9]*\])" type)]
(map first res)))

(defn nested-name [type]
(let [ntypes (list-of-nested-types type)]
(if ntypes
(subs type 0 (- (count type) (count (last ntypes))))
type)))

(defn is-arr? [type]
(boolean (list-of-nested-types type)))

(defn is-dynamic-arr? [type]
(let [ntypes (list-of-nested-types type)]
(and ntypes (zero? (arr-size (last ntypes))))))

(defn static-arr-len [type]
(let [ntypes (list-of-nested-types type)]
(if ntypes
(nested-size (last ntypes))
1)))

(defn static-part-length [type]
(apply * (conj (map nested-size (or (list-of-nested-types type) '("1"))) 32)))

(defn offset-reducer [{:keys [cnt coll]} val]
(let [cnt' (+ cnt val)]
{:cnt cnt'
:coll (conj coll cnt')}))

(defn get-offsets [types]
(let [lengths (map static-part-length types)]
(conj (butlast (:coll (reduce offset-reducer {:cnt 0 :coll []} lengths))) 0)))

(defn hex-to-bytes [hex]
(let [len (* (.toNumber (.toBN utils (subs hex 0 64))) 2)]
(substr hex 64 len)))

(defn dyn-hex-to-value [hex type]
(cond
(str/starts-with? type "bytes")
(str "0x" (hex-to-bytes hex))

(str/starts-with? type "string")
(hex-to-utf8 (hex-to-bytes hex))))

(defn hex-to-bytesM [hex type]
(let [size (int (second (re-matches #"^bytes([0-9]*)" type)))]
(subs hex 0 (* 2 size))))

(defn hex-to-value [hex type]
(cond
(= "bool" type) (= hex "0000000000000000000000000000000000000000000000000000000000000001")
(str/starts-with? type "uint") (hex-to-number hex)
(str/starts-with? type "int") (hex-to-number hex)
(str/starts-with? type "address") (str "0x" (subs hex (- (count hex) 40)))
(str/starts-with? type "bytes") (hex-to-bytesM hex type)))

(defn dec-type [bytes]
(fn [offset type]
(cond
(is-arr? type)

(let [dyn-arr? (is-dynamic-arr? type)
arr-off (js/parseInt (str "0x" (substr bytes (* offset 2) 64)))
len (if dyn-arr?
(js/parseInt (str "0x" (substr bytes (* arr-off 2) 64)))
(static-arr-len type))
arr-start (if dyn-arr? (+ arr-off 32) offset)

nname (nested-name type)
nstatpartlen (static-part-length nname)
rnstatpartlen (* (js/Math.floor (/ (+ nstatpartlen 31) 32)) 32)]
(loop [res [] i 0]
(if (>= i (* len rnstatpartlen))
res
(recur (conj res ((dec-type bytes) (+ arr-start i) nname)) (+ i rnstatpartlen)))))

(or (re-matches #"^bytes(\[([0-9]*)\])*$" type)
(str/starts-with? type "string"))

(let [dyn-off (js/parseInt (str "0x" (substr bytes (* offset 2) 64)))
len (js/parseInt (str "0x" (substr bytes (* dyn-off 2) 64)))
rlen (js/Math.floor (/ (+ len 31) 32))
val (substr bytes (* dyn-off 2) (* (+ rlen 1) 64))]
(dyn-hex-to-value val type))

:else

(let [len (static-part-length type)
val (substr bytes (* offset 2) (* len 2))]
(hex-to-value val type)))))

(defn decode [bytes types]
(let [offsets (get-offsets types)]
(map (dec-type bytes) offsets types)))
26 changes: 26 additions & 0 deletions test/cljs/status_im/test/utils/ethereum/abi_spec.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,29 @@

(is (= (abi-spec/encode "g(uint[][],string[])" [[[1 2] [3]] ["one" "two" "three"]])
"0xad6a3446000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000036f6e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000374776f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057468726565000000000000000000000000000000000000000000000000000000")))

(deftest test-decode
(is (= (abi-spec/decode "000000000000000000000000000000000000000000000000000000005bc741cd00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000013b86dbf1a83c9e6a492914a0ee39e8a5b7eb60700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e516d533152484e4a57414b356e426f6f57454d34654d644268707a35666e325764557473457357754a4b79356147000000000000000000000000000000000000"
["uint256" "bytes" "address" "uint256" "uint256"])
'(1539785165
"0x516d533152484e4a57414b356e426f6f57454d34654d644268707a35666e325764557473457357754a4b79356147"
"0x13b86dbf1a83c9e6a492914a0ee39e8a5b7eb607"
0
0)))

(is (= (abi-spec/decode "00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001"
["uint32" "bool"])
'(69 true)))

(is (= (map abi-spec/hex-to-utf8
(first (abi-spec/decode "61626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000"
["bytes3[2]"])))
'("abc" "def")))

(is (= (abi-spec/decode "0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"
["string" "bool" "uint256[]"])
'("dave" true [1 2 3])))

(is (= (abi-spec/decode "00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000"
["uint" "uint32[]" "bytes10" "bytes"])
'(291 [1110 1929] "31323334353637383930" "0x48656c6c6f2c20776f726c6421"))))

0 comments on commit f8ef431

Please sign in to comment.