cljs implementation of abi-spec
Signed-off-by: yenda <eric@status.im>
This commit is contained in:
parent
0c48d09c71
commit
21cc106ed3
|
@ -74,7 +74,7 @@ import com.android.build.OutputFile
|
|||
* ]
|
||||
*/
|
||||
project.ext.react = [
|
||||
nodeExecutableAndArgs: ["node", "--max-old-space-size=4096"],
|
||||
nodeExecutableAndArgs: ["node", "--max-old-space-size=8192"],
|
||||
entryFile: "index.android.js"
|
||||
]
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"react-native-firebase"
|
||||
"homoglyph-finder"
|
||||
"web3"
|
||||
"web3-utils"
|
||||
"chance"
|
||||
"instabug-reactnative"
|
||||
"react-native-http-bridge"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,11 +25,12 @@
|
|||
"dependencies": {
|
||||
"assert": "1.4.1",
|
||||
"asyncstorage-down": "4.0.1",
|
||||
"@babel/core": "7.0.0-rc.2",
|
||||
"@babel/generator": "7.0.0-rc.2",
|
||||
"@babel/helper-builder-react-jsx": "7.0.0-rc.2",
|
||||
"@babel/plugin-transform-block-scoping": "7.0.0-rc.2",
|
||||
"@babel/register": "7.0.0-rc.2",
|
||||
"@babel/core": "7.0.1",
|
||||
"@babel/generator": "7.0.0",
|
||||
"@babel/helper-builder-react-jsx": "7.0.0",
|
||||
"@babel/plugin-transform-block-scoping": "7.0.0",
|
||||
"@babel/register": "7.0.0",
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"babel-preset-react-native": "5.0.2",
|
||||
"bignumber.js": "github:status-im/bignumber.js#master",
|
||||
"buffer": "3.6.0",
|
||||
|
@ -84,7 +85,8 @@
|
|||
"string_decoder": "0.10.31",
|
||||
"text-encoding": "^0.6.4",
|
||||
"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": {
|
||||
"patch-package": "^5.1.1"
|
||||
|
|
|
@ -1780,7 +1780,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,10 +6,11 @@
|
|||
"start": "node node_modules/react-native/local-cli/cli.js start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "7.0.0-rc.2",
|
||||
"@babel/helper-builder-react-jsx": "7.0.0-rc.2",
|
||||
"@babel/plugin-transform-block-scoping": "7.0.0-rc.2",
|
||||
"@babel/register": "7.0.0-rc.2",
|
||||
"@babel/generator": "7.0.0",
|
||||
"@babel/helper-builder-react-jsx": "7.0.0",
|
||||
"@babel/plugin-transform-block-scoping": "7.0.0",
|
||||
"@babel/register": "7.0.0",
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"@tradle/react-native-http": "2.0.1",
|
||||
"assert": "1.4.1",
|
||||
"asyncstorage-down": "4.0.1",
|
||||
|
@ -67,6 +68,7 @@
|
|||
"string_decoder": "0.10.31",
|
||||
"text-encoding": "^0.6.4",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
(def Web3 (js/require "web3"))
|
||||
(def text-encoding (js/require "text-encoding"))
|
||||
(def js-sha3 (js/require "js-sha3"))
|
||||
(def web3-utils (js/require "web3-utils"))
|
||||
|
|
|
@ -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 two’s 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})))))
|
|
@ -37,6 +37,7 @@
|
|||
[status-im.test.utils.clocks]
|
||||
[status-im.test.utils.ethereum.eip681]
|
||||
[status-im.test.utils.ethereum.core]
|
||||
[status-im.test.utils.ethereum.abi-spec]
|
||||
[status-im.test.utils.ethereum.ens]
|
||||
[status-im.test.utils.ethereum.mnemonic]
|
||||
[status-im.test.utils.random]
|
||||
|
@ -104,6 +105,7 @@
|
|||
'status-im.test.utils.ethereum.eip681
|
||||
'status-im.test.utils.ethereum.core
|
||||
'status-im.test.utils.ethereum.mnemonic
|
||||
'status-im.test.utils.ethereum.abi-spec
|
||||
'status-im.test.utils.ethereum.ens
|
||||
'status-im.test.utils.random
|
||||
'status-im.test.utils.gfycat.core
|
||||
|
|
|
@ -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")))
|
Loading…
Reference in New Issue