Last active
March 23, 2018 21:29
-
-
Save cmditch/459da1c9642c57191b4e6ec3972e4836 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module EvmDecoder exposing (main) | |
import BigInt exposing (BigInt) | |
import Hex | |
import Html exposing (Html, text) | |
import Json.Decode as Decode exposing (Decoder) | |
import Result.Extra as Result | |
import String.Extra as String | |
import Regex | |
main : Html msg | |
main = | |
Decode.decodeString multiSigDecoder testEvmOutput | |
|> toString | |
|> text | |
type alias MultiSigWallet = | |
{ members : List Address | |
, totalFunds : BigInt | |
} | |
multiSigDecoder : Decoder MultiSigWallet | |
multiSigDecoder = | |
evmDecode MultiSigWallet | |
|> andMap (dArray address) | |
|> andMap uint | |
|> toElmDecoder | |
testEvmOutput = | |
"\"0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000003000000000000000000000000ED9878336d5187949E4ca33359D2C47c846c9Dd30000000000000000000000006A987e3C0cd7Ed478Ce18C4cE00a0B313299365B000000000000000000000000aD9178336d523494914ca37359D2456ef123466c\"" | |
type alias EvmDecoder a = | |
Tape -> Result String ( Tape, a ) | |
{-| Tape == (Init Tape, Live Tape) | |
Live Tape: Tape that is being read and eaten up in 32 byte / 64 character chunks | |
Init Tape: Untouched copy of the initial input string, i.e., the full hex return from a JSON RPC Call. | |
This remains untouched during the entire decoding process, | |
and is needed to help grab dynamic solidity values, such as 'bytes', 'address[]', or 'uint256[]' | |
-} | |
type alias Tape = | |
( String, String ) | |
evmDecode : a -> EvmDecoder a | |
evmDecode r = | |
\data -> Ok ( data, r ) | |
runDecoder : EvmDecoder a -> String -> Result String a | |
runDecoder evmDecoder string = | |
remove0x string | |
|> (\a -> ( a, a )) | |
|> evmDecoder | |
|> Result.map Tuple.second | |
toElmDecoder : EvmDecoder a -> Decoder a | |
toElmDecoder = | |
runDecoder >> makeDecoder | |
makeDecoder : (String -> Result String a) -> Decoder a | |
makeDecoder typeCaster = | |
let | |
convert n = | |
case typeCaster n of | |
Ok a -> | |
Decode.succeed a | |
Err error -> | |
Decode.fail error | |
in | |
Decode.string |> Decode.andThen convert | |
-- Decoders | |
uint : EvmDecoder BigInt | |
uint = | |
\( init, t ) -> | |
take64 t | |
|> add0x | |
|> BigInt.fromString | |
|> Result.fromMaybe "Error Decoding Uint into BigInt" | |
|> Result.map (\bi -> ( ( init, drop64 t ), bi )) | |
bool : EvmDecoder Bool | |
bool = | |
let | |
parseBool b = | |
case String.left 63 b |> String.all ((==) '0') of | |
True -> | |
case String.right 1 b of | |
"0" -> | |
Ok False | |
"1" -> | |
Ok True | |
_ -> | |
Err ("Boolean decode error." ++ b ++ " is not boolean.") | |
False -> | |
Err ("Boolean decode error." ++ b ++ " is not boolean.") | |
in | |
\( init, t ) -> | |
take64 t | |
|> parseBool | |
|> Result.map (\bi -> ( ( init, drop64 t ), bi )) | |
address : EvmDecoder Address | |
address = | |
\( init, t ) -> | |
take64 t | |
|> hexToAddress | |
|> Result.map (\address -> ( ( init, drop64 t ), address )) | |
{-| Decode Dynamically Sized Arrays | |
(dArray address) == address[] | |
-} | |
dArray : EvmDecoder a -> EvmDecoder (List a) | |
dArray decoder = | |
\( init, t ) -> | |
take64 t | |
|> buildDynArray init | |
|> Result.map (List.map (unpackDecoder decoder)) | |
|> Result.andThen Result.combine | |
|> Result.map (\list -> ( ( init, drop64 t ), list )) | |
{-| Decode Statically Sized Arrays | |
(sArray 10 uint) == uint256[10] | |
-} | |
sArray : Int -> EvmDecoder a -> EvmDecoder (List a) | |
sArray arrSize decoder = | |
\( init, t ) -> | |
String.left (arrSize * 64) t | |
|> String.break 64 | |
|> List.map (unpackDecoder decoder) | |
|> Result.combine | |
|> Result.map (\list -> ( ( init, String.dropLeft (arrSize * 64) t ), list )) | |
{-| Chain and Map Decoders | |
andMap is the same as `apply` or `<*>` in Haskell, except initial arguments are flipped to help with elm pipeline syntax. | |
-} | |
andMap : EvmDecoder a -> EvmDecoder (a -> b) -> EvmDecoder b | |
andMap dVal dFunc = | |
map2 (\f v -> f v) dFunc dVal | |
map2 : (a -> b -> c) -> EvmDecoder a -> EvmDecoder b -> EvmDecoder c | |
map2 f decA decB = | |
\tape0 -> | |
decA tape0 | |
|> Result.andThen | |
(\( tape1, vA ) -> | |
decB tape1 | |
|> Result.map (Tuple.mapSecond (f vA)) | |
) | |
{-| Takes the index pointer to the beginning of the array data (the first piece being the array length) | |
and the full return data, and slices out the | |
Example - Here is a returns(address[],uint256) | |
0000000000000000000000000000000000000000000000000000000000000040 -- Start index of address[] (starts at byte 64, or the 128th character) | |
0000000000000000000000000000000000000000000000000000000000000123 -- Some Uint256 | |
0000000000000000000000000000000000000000000000000000000000000003 -- Length of address[] | |
000000000000000000000000ED9878336d5187949E4ca33359D2C47c846c9Dd3 -- First Address | |
0000000000000000000000006A987e3C0cd7Ed478Ce18C4cE00a0B313299365B -- Second Address | |
000000000000000000000000aD9178336d523494914ca37359D2456ef123466c -- Third Address | |
buildDynArray fullReturnData startIndex | |
> Ok ["000000000000000000000000ED9878336d5187949E4ca33359D2C47c846c9Dd3", secondAddress, thirdAddress] | |
-} | |
buildDynArray : String -> String -> Result String (List String) | |
buildDynArray fullTape startIndex = | |
let | |
toIntIndex = | |
Hex.fromString >> Result.map ((*) 2) | |
sliceData dataIndex arrLength = | |
String.slice dataIndex (dataIndex + (arrLength * 64)) fullTape | |
in | |
toIntIndex startIndex | |
|> Result.andThen | |
(\index -> | |
String.slice index (index + 64) fullTape | |
|> Hex.fromString | |
|> Result.map (sliceData <| index + 64) | |
) | |
|> Result.map (String.break 64) | |
{-| Applies decoder to value without bothering with Tape State | |
Useful for mapping over lists built from dynamic types | |
-} | |
unpackDecoder : EvmDecoder a -> String -> Result String a | |
unpackDecoder decoder val = | |
decoder ( "", val ) | |
|> Result.map Tuple.second | |
-- Utils | |
type Address | |
= Address String | |
add0x : String -> String | |
add0x str = | |
if String.startsWith "0x" str then | |
str | |
else | |
"0x" ++ str | |
remove0x : String -> String | |
remove0x str = | |
if String.startsWith "0x" str then | |
String.dropLeft 2 str | |
else | |
str | |
take64 : String -> String | |
take64 = | |
String.left 64 | |
drop64 : String -> String | |
drop64 = | |
String.dropLeft 64 | |
hexToAddress : String -> Result String Address | |
hexToAddress str = | |
let | |
addressRegex = | |
Regex.regex "^(0x){1}[0-9a-fA-F]{40}$" | |
address = | |
"0x" ++ String.dropLeft 24 str | |
in | |
if Regex.contains addressRegex address then | |
Ok (Address address) | |
else | |
Err "Invalid ethereum address format" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment