@@ -0,0 +1,982 @@
5+:: deriving and using ethereum keys
17+ (cork pub-from-prv address-from-pub)
20+ =, secp256k1:secp:crypto
25+ ++ sign-typed-transaction
26+ |= [tx=typed-transaction:rpc pk=@]
30+ %0x0 (sign-transaction +.tx pk)
31+ %0x2 (sign-transaction-1559 +.tx pk)
36+ |= [tx=transaction:rpc pk=@]
38+ :: hash the raw transaction data
41+ =+ dat=(encode chain-id.tx 0 0)
43+ [wid (rev 3 wid dat)]
44+ :: sign transaction hash with private key
45+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
46+ :: complete transaction is raw data, with r and s
47+ :: taken from the signature, and v as per eip-155
48+ (encode :(add (mul chain-id.tx 2) 35 v) r s)
53+ tx(to b+20^to.tx, chain-id [v r s ~])
56+ ++ sign-transaction-1559
58+ |= [tx=transaction-1559:rpc pk=@]
61+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
62+ ::NOTE we retrieve y's parity from the v value
63+ (encode-1559 ~ (end 0 v) r s)
64+ :: hash the raw transaction data including leading 0x2
66+ =+ dat=(cat 3 (encode-1559 ~) 0x2)
68+ [wid (rev 3 wid dat)]
71+ |= sig=(unit [y=@ r=@ s=@])
84+ %+ turn ~(tap by access-list)
85+ |= [a=address b=(list @ux)]
86+ l+~[b+20^a l+(turn b |=(c=@ux b+32^c))]
95+::NOTE https://eth.wiki/en/fundamentals/rlp
99+ ::NOTE rlp encoding doesn't really care about leading zeroes,
100+ :: but because we need to disinguish between no-bytes zero
101+ :: and one-byte zero (and also empty list) we end up with
102+ :: this awful type...
105+ $% [%l l=(list item)]
108+ :: +encode-atoms: encode list of atoms as a %l of %b items
110+ ++ encode-atoms ::NOTE deprecated
120+ $(in [%b (met 3 in) in])
123+ ?: &(=(1 wid.b.in) (lte dat.b.in 0x7f))
125+ =- (can 3 ~[b.in [(met 3 -) -]])
126+ (encode-length wid.b.in 0x80)
129+ :: we +can because b+1^0x0 encodes to 0x00
135+ [(max 1 (met 3 -)) -]
139+ (encode-length (roll (turn l head) add) 0xc0)
144+ ?: (lth len 56) (add len off)
146+ :(add (met 3 len) off 55)
148+ :: +decode-atoms: decode expecting a %l of %b items, producing atoms within
153+ =/ i=item (decode dat)
154+ ~| [%unexpected-data i]
158+ ~| [%unexpected-list i]
165+ =/ bytes=(list @) (flop (rip 3 dat))
166+ =? bytes ?=(~ bytes) ~[0]
172+ ~| %rlp-unexpected-end
175+ :: byte in 0x00-0x79 range encodes itself
180+ :: byte in 0x80-0xb7 range encodes string length
183+ =+ len=(sub byt 0x80)
186+ len^(get-value 1 len)
187+ :: byte in 0xb8-0xbf range encodes string length length
190+ =+ led=(sub byt 0xb7)
191+ =+ len=(get-value 1 led)
194+ len^(get-value +(led) len)
195+ :: byte in 0xc0-f7 range encodes list length
198+ =+ len=(sub byt 0xc0)
202+ decode-list(bytes (slag 1 `(list @)`bytes))
203+ :: byte in 0xf8-ff range encodes list length length
206+ =+ led=(sub byt 0xf7)
207+ =+ len=(get-value 1 led)
211+ decode-list(bytes (slag +(led) `(list @)`bytes))
212+ ~| [%rip-not-bloq-3 `@ux`byt]
219+ =+ ^- [don=@ud =item] ::TODO =/
224+ bytes (slag don bytes)
230+ (rep 3 (flop (swag [at to] bytes)))
235+::NOTE https://solidity.readthedocs.io/en/develop/abi-spec.html
239+ :: solidity types. integer bitsizes ignored
251+ [%array-n t=etyp n=@ud]
255+ :: solidity-style typed data. integer bitsizes ignored
257+ $% [%address p=address]
264+ [%array-n p=(list data)]
265+ [%array p=(list data)]
266+ [%bytes-n p=octs] ::TODO just @, because context knows length?
275+ :: encode list of arguments.
279+ (encode-data [%array-n das])
282+ :: encode typed data into ABI bytestring.
287+ ~| [%unsupported-type -.dat]
291+ :: enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])
292+ :: where head and tail are defined for X[i] being of a static type as
293+ :: head(X[i]) = enc(X[i]) and tail(X[i]) = "" (the empty string), or as
294+ :: head(X[i]) = enc(len( head(X[0])..head(X[k-1])
295+ :: tail(X[0])..tail(X[i-1]) ))
296+ :: and tail(X[i]) = enc(X[i]) otherwise.
298+ :: so: if it's a static type, data goes in the head. if it's a dynamic
299+ :: type, a reference goes into the head and data goes into the tail.
301+ :: in the head, we first put a placeholder where references need to go.
302+ =+ hol=(reap 64 'x')
306+ ?. (is-dynamic-type d) ^$(dat d)
311+ ?. (is-dynamic-type d) ""
313+ :: once we know the head and tail, we can fill in the references in head.
314+ =- (weld nes `tape`(zing tas))
316+ =+ led=(lent (zing hes))
318+ |= [t=tape i=@ud nes=tape]
320+ :: if no reference needed, just put the data.
321+ ?. =(t hol) (weld nes t)
322+ :: calculate byte offset of data we need to reference.
324+ =- (div - 2) :: two hex digits per byte.
325+ %+ add led :: count head, and
326+ %- lent %- zing :: count all tail data
327+ (scag i tas) :: preceding ours.
328+ =+ ref=^$(dat [%uint ofs])
329+ :: shouldn't hit this unless we're sending over 2gb of data?
330+ ~| [%weird-ref-lent (lent ref)]
331+ ?> =((lent ref) (lent hol))
334+ %array :: where X has k elements (k is assumed to be of type uint256):
335+ :: enc(X) = enc(k) enc([X[1], ..., X[k]])
336+ :: i.e. it is encoded as if it were an array of static size k, prefixed
337+ :: with the number of elements.
338+ %+ weld $(dat [%uint (lent p.dat)])
339+ $(dat [%array-n p.dat])
342+ :: enc(X) is the sequence of bytes in X padded with zero-bytes to a
344+ :: Note that for any X, len(enc(X)) is a multiple of 32.
345+ ~| [%bytes-n-too-long max=32 actual=p.p.dat]
347+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
349+ %bytes :: of length k (which is assumed to be of type uint256)
350+ :: enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
351+ :: uint256 followed by the actual value of X as a byte sequence, followed
352+ :: by the minimum number of zero-bytes such that len(enc(X)) is a
354+ %+ weld $(dat [%uint p.p.dat])
355+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
358+ :: enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this value is
359+ :: interpreted as of bytes type and encoded further. Note that the length
360+ :: used in this subsequent encoding is the number of bytes of the utf-8
361+ :: encoded string, not its number of characters.
362+ $(dat [%bytes (lent p.dat) (swp 3 (crip p.dat))])
365+ :: enc(X) is the big-endian encoding of X, padded on the higher-order
366+ :: (left) side with zero-bytes such that the length is a multiple of 32
368+ (pad-to-multiple (render-hex-bytes (as-octs p.dat)) 64 %left)
371+ :: as in the uint8 case, where 1 is used for true and 0 for false
372+ $(dat [%uint ?:(p.dat 1 0)])
375+ :: as in the uint160 case
376+ $(dat [%uint `@ud`p.dat])
382+ ?=(?(%string %bytes %array) -.a)
383+ &(!=((lent p.a) 0) (lien p.a is-dynamic-type))
387+ ++ decode-topics decode-arguments
390+ :: rex: string of hex bytes with leading 0x.
391+ |* [rex=@t tys=(list etyp)]
392+ =- (decode-arguments - tys)
398+ |* [wos=(list @) tys=(list etyp)]
399+ =/ wos=(list @) wos :: get rid of tmi
401+ =< (decode-from 0 tys)
404+ |* [win=@ud tys=(list etyp)]
407+ [dat $(win nin, tys t.tys)]
408+ (decode-one win ~[i.tys])
411+ ::NOTE we take (list etyp) even though we only operate on
412+ :: a single etyp as a workaround for urbit/arvo#673
413+ |* [win=@ud tys=(list etyp)]
414+ =- [nin dat]=- ::NOTE ^= regular form broken
417+ =+ wor=(snag win wos)
419+ ~| [%unsupported-type typ]
422+ ?(%address %bool %uint) :: %int %real %ureal
432+ [nin (trip (swp 3 q.dat))]
436+ :: find the word index of the actual data.
437+ =/ lic=@ud (div wor 32)
438+ :: learn the bytelength of the data.
439+ =/ len=@ud (snag lic wos)
440+ (decode-bytes-n +(lic) len)
443+ :- (add win +((div (dec n.typ) 32)))
444+ (decode-bytes-n win n.typ)
448+ :: find the word index of the actual data.
450+ :: read the elements from their location.
452+ %^ decode-array-n ~[t.typ] +(win)
456+ (decode-array-n ~[t.typ] win n.typ)
460+ |= [fro=@ud bys=@ud]
462+ :: parse {bys} bytes from {fro}.
467+ ?:(=(0 -) - (sub 32 -))
470+ =- (swag [fro -] wos)
471+ +((div (dec bys) 32))
474+ ::NOTE we take (list etyp) even though we only operate on
475+ :: a single etyp as a workaround for urbit/arvo#673
476+ ::NOTE careful! produces lists without type info
478+ |* [tys=(list etyp) fro=@ud len=@ud]
481+ ?: =(len 0) [fro (flop `(list)`res)]
482+ =+ (decode-one fro ~[i.tys]) :: [nin=@ud dat=*]
483+ $(res ^+(res [dat res]), fro nin, len (dec len))
487+:: communicating with rpc nodes
488+::NOTE https://github.com/ethereum/wiki/wiki/JSON-RPC
499+ arguments=(list data)
502+ :: raw transaction data
503+ +$ typed-transaction
504+ $% [%0x0 transaction]
505+ [%0x2 transaction-1559]
521+ max-priority-gas-fee=@ud
527+ access-list=(jar address @ux)
530+ :: ethereum json rpc api
532+ :: supported requests.
534+ $% [%eth-block-number ~]
535+ [%eth-call cal=call deb=block]
540+ top=(list ?(@ux (list @ux)))
542+ [%eth-get-block-by-number bon=@ud txs=?]
543+ [%eth-get-filter-logs fid=@ud]
548+ top=(list ?(@ux (list @ux)))
550+ $: %eth-get-logs-by-hash
553+ top=(list ?(@ux (list @ux)))
555+ [%eth-get-filter-changes fid=@ud]
556+ [%eth-get-transaction-by-hash txh=@ux]
557+ [%eth-get-transaction-count adr=address =block]
558+ [%eth-get-balance adr=address =block]
559+ [%eth-get-transaction-receipt txh=@ux]
560+ [%eth-send-raw-transaction dat=@ux]
563+ ::TODO clean up & actually use
566+ [%eth-new-filter fid=@ud]
567+ [%eth-get-filter-logs los=(list event-log)]
568+ [%eth-get-logs los=(list event-log)]
569+ [%eth-get-logs-by-hash los=(list event-log)]
570+ [%eth-got-filter-changes los=(list event-log)]
571+ [%eth-transaction-hash haz=@ux]
574+ ++ transaction-result
575+ $: block-hash=(unit @ux)
576+ block-number=(unit @ud)
577+ transaction-index=(unit @ud)
584+ $: :: null for pending logs
588+ transaction-index=@ud
589+ transaction-hash=@ux
599+ :: For standard events, the first topic is the event signature
600+ :: hash. For anonymous events, the first topic is the first
601+ :: indexed argument.
602+ :: Note that this does not support the "anonymous event with
603+ :: zero topics" case. This has dubious usability, and using
604+ :: +lest instead of +list saves a lot of ?~ checks.
609+ :: data for eth_call.
611+ $: from=(unit address)
614+ gas-price=(unit @ud)
619+ :: minimum data needed to construct a read call
620+ ++ proto-read-request
626+ :: block to operate on.
629+ [%label l=?(%earliest %latest %pending)]
639+ ::TODO should this check to see if the data matches the function signature?
640+ =- :(weld "0x" - (encode-args arguments))
642+ %+ render-hex-bytes 32
643+ %- keccak-256:keccak:crypto
644+ (as-octs:mimes:html function)
646+ :: building requests
650+ |= [url=purl jon=json]
654+ ~['Content-Type'^['application/json']~]
655+ (some (as-octt (en-json:html jon)))
656+ :: +light-json-request: like json-request, but for %l
658+ :: TODO: Exorcising +purl from our system is a much longer term effort;
659+ :: get the current output types for now.
661+ ++ light-json-request
662+ |= [url=purl:eyre jon=json]
666+ (crip (en-purl:html url))
667+ ~[['content-type' 'application/json']]
668+ (some (as-octt (en-json:html jon)))
671+ ++ batch-read-request
672+ |= req=(list proto-read-request)
674+ a+(turn req read-request)
677+ |= proto-read-request
679+ %+ request-to-json id
682+ [~ to ~ ~ ~ `tape`(encode-call function arguments)]
687+ |= [riq=(unit @t) req=request]
690+ =; r=[met=@t pas=(list json)]
691+ ::TODO should use request-to-json:rpc:jstd,
692+ :: and probably (fall riq -.req)
696+ ::TODO would just jamming the req noun for id be a bad idea?
702+ ['eth_blockNumber' ~]
706+ :~ (eth-call-to-json cal.req)
707+ (block-to-json deb.req)
713+ :- %o %- ~(gas by *(map @t json))
715+ ^- (list (unit (pair @t json)))
717+ `['fromBlock' (block-to-json u.fro.req)]
720+ `['toBlock' (block-to-json u.tob.req)]
723+ ?: =(0 (lent adr.req)) ~
725+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
727+ (turn adr.req (cork address-to-hex tape))
731+ (topics-to-json top.req)
734+ %eth-get-block-by-number
735+ :- 'eth_getBlockByNumber'
736+ :~ (tape (num-to-hex bon.req))
740+ %eth-get-filter-logs
741+ ['eth_getFilterLogs' (tape (num-to-hex fid.req)) ~]
746+ :- %o %- ~(gas by *(map @t json))
748+ ^- (list (unit (pair @t json)))
750+ `['fromBlock' (block-to-json u.fro.req)]
753+ `['toBlock' (block-to-json u.tob.req)]
755+ ?: =(0 (lent adr.req)) ~
757+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
759+ (turn adr.req (cork address-to-hex tape))
763+ (topics-to-json top.req)
766+ %eth-get-logs-by-hash
769+ %- ~(gas by *(map @t json))
771+ ^- (list (unit (pair @t json)))
772+ :~ `['blockHash' (tape (transaction-to-hex has.req))]
774+ ?: =(0 (lent adr.req)) ~
776+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
778+ (turn adr.req (cork address-to-hex tape))
782+ (topics-to-json top.req)
785+ %eth-get-filter-changes
786+ ['eth_getFilterChanges' (tape (num-to-hex fid.req)) ~]
788+ %eth-get-transaction-count
789+ :- 'eth_getTransactionCount'
790+ :~ (tape (address-to-hex adr.req))
791+ (block-to-json block.req)
796+ :~ (tape (address-to-hex adr.req))
797+ (block-to-json block.req)
800+ %eth-get-transaction-by-hash
801+ ['eth_getTransactionByHash' (tape (transaction-to-hex txh.req)) ~]
803+ %eth-get-transaction-receipt
804+ ['eth_getTransactionReceipt' (tape (transaction-to-hex txh.req)) ~]
806+ %eth-send-raw-transaction
807+ ['eth_sendRawTransaction' (tape (num-to-hex dat.req)) ~]
814+ :- %o %- ~(gas by *(map @t json))
816+ ^- (list (unit (pair @t json)))
818+ `['from' (tape (address-to-hex u.from.cal))]
820+ `['to' (tape (address-to-hex to.cal))]
823+ `['gas' (tape (num-to-hex u.gas.cal))]
826+ `['gasPrice' (tape (num-to-hex u.gas-price.cal))]
829+ `['value' (tape (num-to-hex u.value.cal))]
832+ `['data' (tape data.cal)]
839+ %number s+(crip '0' 'x' ((x-co:co 1) n.dob))
844+ |= tos=(list ?(@ux (list @ux)))
849+ (cury render-hex-bytes 32)
860+ :: parsing responses
862+ ::TODO ++ parse-response |= json ^- response
870+ ++ parse-eth-new-filter-res parse-hex-result
872+ ++ parse-eth-block-number parse-hex-result
874+ ++ parse-transaction-hash parse-hex-result
876+ ++ parse-eth-get-transaction-count parse-hex-result
878+ ++ parse-eth-get-balance parse-hex-result
881+ (ar:dejs:format parse-event-log)
888+ :~ =- ['logIndex'^(cu - (mu so))]
891+ =- ``((ou -) log) ::TODO not sure if elegant or hacky.
892+ :~ 'logIndex'^(un (cu hex-to-num so))
893+ 'transactionIndex'^(un (cu hex-to-num so))
894+ 'transactionHash'^(un (cu hex-to-num so))
895+ 'blockNumber'^(un (cu hex-to-num so))
896+ 'blockHash'^(un (cu hex-to-num so))
900+ address+(cu hex-to-num so)
903+ =- topics+(cu - (ar so))
908+ (turn t.r hex-to-num)
911+ ++ parse-transaction-result
915+ ^- transaction-result
917+ :~ 'blockHash'^_~ :: TODO: fails if maybe-num?
918+ 'blockNumber'^maybe-num
919+ 'transactionIndex'^maybe-num
920+ from+(cu hex-to-num so)
934+::TODO give them better homes!
943+ (as-octs:mimes:html n)
949+ (render-hex-bytes 20 `@`a)
951+++ transaction-to-hex
955+ (render-hex-bytes 32 h)
963+ :: atom to string of hex bytes without 0x prefix and dots.
966+ ((x-co:co (mul 2 p.a)) q.a)
969+ |= [wat=tape mof=@ud wer=?(%left %right)]
972+ ?: =(0 len) (reap mof '0')
973+ =+ mad=(mod len mof)
975+ =+ tad=(reap (sub mof mad) '0')
977+ ?:(?=(%left wer) [tad wat] [wat tad])
981+ (rash (rsh [3 2] a) hex)