cljs implementation of abi-spec

Signed-off-by: yenda <eric@status.im>
This commit is contained in:
Eric Dvorsak 2018-08-11 04:39:58 +02:00 committed by yenda
parent 0c48d09c71
commit 21cc106ed3
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
11 changed files with 4793 additions and 1941 deletions

View File

@ -74,7 +74,7 @@ import com.android.build.OutputFile
* ] * ]
*/ */
project.ext.react = [ project.ext.react = [
nodeExecutableAndArgs: ["node", "--max-old-space-size=4096"], nodeExecutableAndArgs: ["node", "--max-old-space-size=8192"],
entryFile: "index.android.js" entryFile: "index.android.js"
] ]

View File

@ -22,6 +22,7 @@
"react-native-firebase" "react-native-firebase"
"homoglyph-finder" "homoglyph-finder"
"web3" "web3"
"web3-utils"
"chance" "chance"
"instabug-reactnative" "instabug-reactnative"
"react-native-http-bridge" "react-native-http-bridge"

File diff suppressed because it is too large Load Diff

View File

@ -25,11 +25,12 @@
"dependencies": { "dependencies": {
"assert": "1.4.1", "assert": "1.4.1",
"asyncstorage-down": "4.0.1", "asyncstorage-down": "4.0.1",
"@babel/core": "7.0.0-rc.2", "@babel/core": "7.0.1",
"@babel/generator": "7.0.0-rc.2", "@babel/generator": "7.0.0",
"@babel/helper-builder-react-jsx": "7.0.0-rc.2", "@babel/helper-builder-react-jsx": "7.0.0",
"@babel/plugin-transform-block-scoping": "7.0.0-rc.2", "@babel/plugin-transform-block-scoping": "7.0.0",
"@babel/register": "7.0.0-rc.2", "@babel/register": "7.0.0",
"@babel/preset-env": "7.1.0",
"babel-preset-react-native": "5.0.2", "babel-preset-react-native": "5.0.2",
"bignumber.js": "github:status-im/bignumber.js#master", "bignumber.js": "github:status-im/bignumber.js#master",
"buffer": "3.6.0", "buffer": "3.6.0",
@ -84,7 +85,8 @@
"string_decoder": "0.10.31", "string_decoder": "0.10.31",
"text-encoding": "^0.6.4", "text-encoding": "^0.6.4",
"url": "0.10.3", "url": "0.10.3",
"web3": "git+https://github.com/status-im/web3.js.git#feature/chat-api" "web3": "github:status-im/web3.js#feature/shhext",
"web3-utils": "1.0.0-beta.36"
}, },
"devDependencies": { "devDependencies": {
"patch-package": "^5.1.1" "patch-package": "^5.1.1"

View File

@ -1780,7 +1780,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "set -o errexit\nexport NODE_BINARY=\"node --max-old-space-size=4096\"\n../node_modules/react-native/scripts/react-native-xcode.sh"; shellScript = "set -o errexit\nexport NODE_BINARY=\"node --max-old-space-size=8192\"\n../node_modules/react-native/scripts/react-native-xcode.sh";
}; };
2EAC54E16AB243C3EBBFE1BA /* [CP] Check Pods Manifest.lock */ = { 2EAC54E16AB243C3EBBFE1BA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,11 @@
"start": "node node_modules/react-native/local-cli/cli.js start" "start": "node node_modules/react-native/local-cli/cli.js start"
}, },
"dependencies": { "dependencies": {
"@babel/generator": "7.0.0-rc.2", "@babel/generator": "7.0.0",
"@babel/helper-builder-react-jsx": "7.0.0-rc.2", "@babel/helper-builder-react-jsx": "7.0.0",
"@babel/plugin-transform-block-scoping": "7.0.0-rc.2", "@babel/plugin-transform-block-scoping": "7.0.0",
"@babel/register": "7.0.0-rc.2", "@babel/register": "7.0.0",
"@babel/preset-env": "7.1.0",
"@tradle/react-native-http": "2.0.1", "@tradle/react-native-http": "2.0.1",
"assert": "1.4.1", "assert": "1.4.1",
"asyncstorage-down": "4.0.1", "asyncstorage-down": "4.0.1",
@ -67,6 +68,7 @@
"string_decoder": "0.10.31", "string_decoder": "0.10.31",
"text-encoding": "^0.6.4", "text-encoding": "^0.6.4",
"url": "0.10.3", "url": "0.10.3",
"web3": "git+https://github.com/status-im/web3.js.git#feature/chat-api" "web3": "https://github.com/status-im/web3.js.git#feature/shhext",
"web3-utils": "1.0.0-beta.36"
} }
} }

View File

@ -8,3 +8,4 @@
(def Web3 (js/require "web3")) (def Web3 (js/require "web3"))
(def text-encoding (js/require "text-encoding")) (def text-encoding (js/require "text-encoding"))
(def js-sha3 (js/require "js-sha3")) (def js-sha3 (js/require "js-sha3"))
(def web3-utils (js/require "web3-utils"))

View File

@ -0,0 +1,269 @@
(ns status-im.utils.ethereum.abi-spec
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.js-dependencies :as dependencies]))
;; Utility functions for encoding
(def utils dependencies/web3-utils)
(defn right-pad [x]
(let [len (count x)
to-pad (- 64 (mod len 64))]
(if (= 64 to-pad)
x
(.rightPad utils x (+ len to-pad)))))
(defn left-pad [x]
(let [len (count x)
to-pad (- 64 (mod len 64))]
(if (= 64 to-pad)
x
(.leftPad utils x (+ len to-pad)))))
(defn to-two-complement [x]
(subs (.toTwosComplement utils x) 2))
(defn from-utf8 [x]
(subs (.fromUtf8 utils x) 2))
(defn bytes-to-hex [x]
(subs (.bytesToHex utils x) 2))
(defn number-to-hex [x]
(subs (.numberToHex utils x) 2))
(defn sha3 [s]
(.sha3 utils (str s)))
(defn is-hex? [value]
(string/starts-with? value "0x"))
;; Encoder for parsed abi spec
(defmulti enc :type)
;; bool: as in the uint8 case, where 1 is used for true and 0 for false
(defmethod enc :bool
[{:keys [value]}]
(left-pad (if value "1" "0")))
;; int<M>: enc(X) is the big-endian twos complement encoding of X, padded on the
;; higher-order (left) side with 0xff for negative X and with zero bytes for
;; positive X such that the length is 32 bytes.
(defmethod enc :int
[{:keys [value]
:or {size 256}}]
(to-two-complement value))
;; uint<M>: enc(X) is the big-endian encoding of X, padded on the
;; higher-order (left) side with zero-bytes such that the length is 32 bytes.
(defmethod enc :uint
[{:keys [value]
:or {size 256}}]
(left-pad (number-to-hex value)))
;; address: as in the uint160 case
(defmethod enc :address
[{:keys [value]}]
(right-pad value))
;; bytes, of length k (which is assumed to be of type uint256):
;; enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
;; uint256 followed by the actual value of X as a byte sequence,
;; followed by the minimum number of zero-bytes such that len(enc(X))
;; is a multiple of 32.
;; bytes<M>: enc(X) is the sequence of bytes in X padded with trailing
;; zero-bytes to a length of 32 bytes.
(defmethod enc :bytes
[{:keys [value size dynamic?]
:or {size 256}}]
;; in the exemples of the abi specifications strings are passed for
;; bytes parameters, in our ens resolver we pass encoded bytes directly
;; for namehash, this handles both cases by checking if the value is already
;; hex
(let [encoded-value? (is-hex? value)
encoded-value (if encoded-value?
(subs value 2)
(from-utf8 value))]
(str (when dynamic? (enc {:type :int :value (if encoded-value?
(count encoded-value)
(/ (count encoded-value) 2))}))
(right-pad encoded-value))))
;; string: enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this
;; value is interpreted as of bytes type and encoded further.
;; Note that the length used in this subsequent encoding is the number
;; of bytes of the utf-8 encoded string, not its number of characters.
(defmethod enc :string
[{:keys [value dynamic?]}]
(enc {:type :bytes :value value :dynamic? dynamic?}))
;; fixed<M>x<N>: enc(X) is enc(X * 10**N) where X * 10**N is
;; interpreted as a int256.
(defmethod enc :fixed
[{:keys [value size power]
:or {size 128
power 18}}]
(enc {:type :int
:value (* value (Math/pow 10 power))}))
;; ufixed: as in the ufixed128x18 case
(defmethod enc :ufixed
[{:keys [value size power]
:or {size 128
power 18}}]
(enc {:type :uint
:value (* value (Math/pow 10 power))}))
;; T[k] for any T and k:
;; enc(X) = enc((X[0], ..., X[k-1]))
;; i.e. it is encoded as if it were a tuple with k elements of the same type.
;; T[] where X has k elements (k is assumed to be of type uint256):
;; enc(X) = enc(k) enc([X[0], ..., X[k-1]])
;; i.e. it is encoded as if it were an array of static size k,
;; prefixed with the number of elements.
(defmethod enc :array
[{:keys [value dynamic? array-of] :as x}]
(str (when dynamic?
(enc {:type :int
:value (count value)}))
(enc {:type :tuple
:value (map #(assoc array-of :value %)
value)})))
;; (T1,...,Tk) for k >= 0 and any types T1, …, Tk
;; enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
;; where X = (X(1), ..., X(k))
;; for Ti being static type:
;; head(X(i)) = enc(X(i))
;; tail(X(i)) = "" (the empty string)
;; for dynamic types:
;; head(X(i)) = enc(len(head(X(1)) ... head(X(k))
;; tail(X(1)) ... tail(X(i-1)) ))
;; tail(X(i)) = enc(X(i))
;; Note that in the dynamic case, head(X(i)) is well-defined since
;; the lengths of the head parts only depend on the types and not
;; the values. Its value is the offset of the beginning of tail(X(i))
;; relative to the start of enc(X).
(defmethod enc :tuple
[{:keys [value]}]
(let [[len x] (reduce
(fn [[len acc] {:keys [type value] :as x}]
(let [enc-x (enc x)]
(if (:dynamic? x)
[(+ len 32)
(conj acc (assoc x :tail enc-x))]
[(+ len (/ (count enc-x) 2))
(conj acc (assoc x :head enc-x))])))
[0 []]
value)
[_ heads tails] (reduce (fn [[len heads tails] {:keys [head tail] :as x}]
(if tail
[(+ len (/ (count tail) 2))
(conj heads (enc {:type :int :value len}))
(conj tails tail)]
[len
(conj heads head)
tails]))
[len [] []]
x)]
(apply str (concat heads tails))))
;;
;; Parser for method signatures
;;
(spec/def ::params (spec/* (spec/cat ::param ::param
::separator (spec/? ::comma))))
(spec/def ::param (spec/cat ::type ::string
::size (spec/? ::number)
::x (spec/? ::x)
::power (spec/? ::number)
::array (spec/* ::array)))
(spec/def ::array (spec/cat ::open-bracket ::open-bracket
::size (spec/? ::number)
::close-bracket ::close-bracket))
(spec/def ::x #{\x})
(spec/def ::open-bracket #{\[})
(spec/def ::close-bracket #{\]})
(spec/def ::comma #{\,})
(spec/def ::number int?)
(spec/def ::string string?)
(defn- single-char [code]
(if-let [m (#{\, \[ \] \x} (first code))]
[1 m]))
(defn- number [code]
(if-let [m (re-find #"^[0-9]+" code)]
[(count m) (js/parseInt m)]))
(defn- string [s]
(if-let [m (re-find #"^[a-z]+" s)]
[(count m) m]))
(defn tokenise [code]
(if (seq code)
(if-let [[len token] (or (string code)
(single-char code)
(number code))]
(cons token (tokenise (subs code len)))
(throw (ex-info "Unexpected token" {:code code})))))
;; Definition: The following types are called “dynamic”:
;; - bytes
;; - string
;; - T[] for any T
;; - T[k] for any dynamic T and any k >= 0
;; - (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k
(defn parse-param
"Takes a parsed parameter and returns a parameter that can
be encoded by associng the :dynamic? key for dynamic parameters
and recursively defining arrays"
[{::keys [type size array power] :as param}]
(if array
(let [{::keys [size]} (last array)]
{:type :array
:dynamic? (nil? size)
:array-of (parse-param (update param ::array butlast))})
(let [type (keyword type)
param {:type type
:dynamic? (or (= type :string)
(and (= type :bytes)
(nil? size)))
:size size}]
(if power
(assoc param :power power)
param))))
(defn parse-params [method-signature]
(let [tokens (tokenise (second (re-find #"\((.*)\)" method-signature)))
params (spec/conform ::params tokens)]
(if (spec/invalid? params)
(spec/explain-data ::params tokens)
(map #(parse-param (::param %))
params))))
(defn signature->method-id [signature]
(apply str (take 10 (sha3 signature))))
(defn encode [method params]
(let [method-id (signature->method-id method)]
(let [params (map #(assoc %1 :value %2)
(parse-params method)
params)]
(str method-id (enc {:type :tuple
:value params})))))

View File

@ -37,6 +37,7 @@
[status-im.test.utils.clocks] [status-im.test.utils.clocks]
[status-im.test.utils.ethereum.eip681] [status-im.test.utils.ethereum.eip681]
[status-im.test.utils.ethereum.core] [status-im.test.utils.ethereum.core]
[status-im.test.utils.ethereum.abi-spec]
[status-im.test.utils.ethereum.ens] [status-im.test.utils.ethereum.ens]
[status-im.test.utils.ethereum.mnemonic] [status-im.test.utils.ethereum.mnemonic]
[status-im.test.utils.random] [status-im.test.utils.random]
@ -104,6 +105,7 @@
'status-im.test.utils.ethereum.eip681 'status-im.test.utils.ethereum.eip681
'status-im.test.utils.ethereum.core 'status-im.test.utils.ethereum.core
'status-im.test.utils.ethereum.mnemonic 'status-im.test.utils.ethereum.mnemonic
'status-im.test.utils.ethereum.abi-spec
'status-im.test.utils.ethereum.ens 'status-im.test.utils.ethereum.ens
'status-im.test.utils.random 'status-im.test.utils.random
'status-im.test.utils.gfycat.core 'status-im.test.utils.gfycat.core

View File

@ -0,0 +1,19 @@
(ns status-im.test.utils.ethereum.abi-spec
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.ethereum.abi-spec :as abi-spec]))
(deftest test-encode
(is (= (abi-spec/encode "baz(uint32,bool)" [69 true])
"0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001"))
(is (= (abi-spec/encode "bar(bytes3[2])" [["abc" "def"]])
"0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000"))
(is (= (abi-spec/encode "sam(bytes,bool,uint256[])" ["dave" true [1 2 3]])
"0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"))
(is (= (abi-spec/encode "f(uint256,uint32[],bytes10,bytes)" [0x123 [0x456 0x789] "1234567890" "Hello, world!"])
"0x8be6524600000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000"))
(is (= (abi-spec/encode "g(uint[][],string[])" [[[1 2] [3]] ["one" "two" "three"]])
"0xad6a3446000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000036f6e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000374776f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057468726565000000000000000000000000000000000000000000000000000000")))