CAD03 is the binary data format underlying the lattice of Convex. This a first version of a partial decoder in Clojure and Javascript (via Squint)
More information on the format here https://docs.convex.world/docs/cad/encoding
CAD03 is the binary data format underlying the lattice of Convex. This a first version of a partial decoder in Clojure and Javascript (via Squint)
More information on the format here https://docs.convex.world/docs/cad/encoding
| (ns dev.jeroenvandijk.convex.encoding | |
| (:require [clojure.string :as str])) | |
| ;; Run with clojure or squint (cljs) | |
| ;; clj encoding.cljc | |
| ;; => [{:address 12} 51 (transfer {:address 13} 1000)] | |
| ;; npm init -y | |
| ;; npm install squint-cljs@latest | |
| ;; npx squint run encoding.cljc | |
| ;; => [{:address 12} 51 ("transfer", {:address 13}, 1000)] | |
| (defn bytes->long [bytes] | |
| #?(:clj (.longValue (BigInteger. (byte-array bytes))) | |
| :cljs (reduce (fn [acc b] (+ (* acc 256) b)) bytes))) | |
| (defn bytes->string [bytes] | |
| #?(:clj (String. (byte-array bytes) "UTF-8") | |
| :cljs (apply js/String.fromCharCode bytes))) | |
| (def integer-tag 0x10) | |
| (def big-integer-tag 0x19) | |
| (def double-tag 0x1d) | |
| (defn read-value | |
| ([[tag & encoding]] | |
| (read-value tag encoding)) | |
| ([tag encoding] | |
| ;; Mostly ported from https://github.com/Convex-Dev/convex/blob/develop/convex-core/src/main/java/convex/core/data/CAD3Encoder.java#L39 | |
| (letfn [(read-dense-record [_tag encoding] | |
| (let [n (first encoding)] | |
| (first (reduce (fn [[acc [t & enc]] _] | |
| (let [[v enc0] (read-value t enc)] | |
| [(conj acc v) enc0])) | |
| [[] | |
| (rest encoding)] | |
| (range 0 n))))) | |
| (vlq-continues-from? [octet] | |
| (not (zero? (bit-and (bit-and octet 0xFF) 0x80)))) | |
| (read-vlq-count [octet encoding] | |
| (let [init-result (bit-and octet 0x7f)] | |
| (loop [[octet & tail :as enc0] encoding | |
| result init-result | |
| bits 7] | |
| (if (vlq-continues-from? octet) | |
| ;; continue while high bit of byte set | |
| (recur tail | |
| (bit-or (bit-shift-left result 7) | |
| (bit-and octet 0x7f)) | |
| (+ bits 7)) | |
| [result enc0])))) | |
| (read-extension [tag encoding] | |
| (case tag | |
| 0xEA | |
| (let [[addr enc] (read-vlq-count (first encoding) (rest encoding))] | |
| [{:address addr} enc]))) | |
| (read-data-structure [tag encoding] | |
| (case tag | |
| 0x81 ;; List | |
| (let [n (first encoding)] | |
| (reduce (fn [[acc [t & enc]] _] | |
| (let [[v enc0] (read-value t enc)] | |
| [(conj acc v) enc0])) | |
| [() | |
| (rest encoding)] | |
| (range 0 n))))) | |
| (read-basic-object [tag encoding] | |
| (case tag | |
| 0x30 (throw (ex-info "TODO String" {})) | |
| 0x32 (let [len (bit-and 0xff (first encoding))] | |
| [(#?(:clj symbol :cljs identity) (bytes->string (take len (drop 1 encoding)))) | |
| (drop (inc len) encoding)]))) | |
| (read-numeric [tag encoding] | |
| (cond | |
| (< tag big-integer-tag) (let [nbytes (- tag integer-tag)] | |
| [(bytes->long (take nbytes encoding)) | |
| (drop nbytes encoding)]) | |
| (== tag big-integer-tag) (throw (ex-info ":read-big-integer" {})) | |
| (== tag double-tag) (throw (ex-info ":read-double" {}))))] | |
| (case (bit-shift-right tag 4) | |
| 1 (read-numeric tag encoding) | |
| 3 (read-basic-object tag encoding) | |
| 8 (read-data-structure tag encoding) | |
| 13 (read-dense-record tag encoding) | |
| 14 (read-extension tag encoding))))) | |
| (defn hex->bytes | |
| "Convert a hex string into a Java byte[]." | |
| [hex] | |
| #?(:clj (let [hex (str/replace hex #"^0x" "") ; optional | |
| len (/ (count hex) 2) | |
| out (byte-array len)] | |
| (doseq [i (range len)] | |
| (let [byte-str (.substring hex (* 2 i) (+ (* 2 i) 2)) | |
| b (unchecked-byte (Integer/parseInt byte-str 16))] | |
| (aset out i b))) | |
| out) | |
| :cljs (let [len (/ (count hex) 2)] | |
| (mapv (fn [i] | |
| (js/parseInt (.substr hex (* i 2) 2) 16)) | |
| (range 0 len))))) | |
| (defn bytes->u8-vec | |
| "Convert a Java byte[] to a vector of 0–255 ints." | |
| [^bytes ba] | |
| (mapv #(bit-and % 0xFF) ba)) | |
| (defn byte->hex [b] | |
| (format "0x%x" b)) | |
| (do #_comment | |
| (def hex-hash "d003ea0c113381031203e8ea0d32087472616e73666572") | |
| (def js-bytes | |
| (->> (hex->bytes hex-hash) | |
| bytes->u8-vec)) | |
| (prn (read-value js-bytes)) ;=> [{:address 12} 51 (transfer {:address 13} 1000)] | |
| ;; TODO collect more encodings, maybe from CVM code? | |
| :-) | |