Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
418 lines (325 sloc) 10.5 KB
module EthABI.Encode
exposing
( encode
, int256
, uint256
, bytes32
, bool
, append
, tuple
, array
, dynamic_array
, bytes
, string
, Encoder
-- , unsafeBigIntToHexStr
-- , padRightToNearestMultipleOf32Bytes
-- , partialEncode
-- TODO
)
import BigInt exposing (BigInt)
import Char
import Hex
import Result.Extra
import List.Extra
import EthABI.Types.Hexstring
import EthABI.Types.Bytes
import EthABI.Types.Bytes32
import EthABI.Types.Int exposing (Int256)
import EthABI.Types.UInt exposing (UInt256)
-- import EthABI.Types exposing (hexstring, Int256, UInt256, int256ToBigInt, uint256ToBigInt, bytes)
import EthABI.Internal exposing (ensure, Hexstring, Bytes32(..), Bytes(..))
-- for internal use only
-- TODO better naming
type alias Hexstring =
String
{-| An Encoder turns an 'a' into a pair of Hexstrings.
The final 'encode' function finally concatenates these two, but it is important for them to be kept separate because if multiple encoders run one-after-the-other, content needs to be added in-between the two.
-}
type alias Encoder =
List EncodedValue
type EncodedValue
= Normal Hexstring
| DynamicReference Hexstring
encode : Encoder -> EthABI.Types.Hexstring.Hexstring
encode encoder =
encoder
|> resolveDynamicReferences
|> EthABI.Internal.Hexstring
resolveDynamicReferences : List EncodedValue -> String
resolveDynamicReferences values =
let
head_length =
bytesLength values
|> Debug.log "head_length"
concatTuple ( a, b ) =
a ++ b
in
values
|> List.foldl (resolveDynamicReferencesImpl head_length) ("", "")
|> concatTuple
resolveDynamicReferencesImpl : BigInt -> EncodedValue -> (String, String) -> (String, String)
resolveDynamicReferencesImpl head_length elem (resolved_head, resolved_tail) =
case elem of
Normal str ->
(resolved_head ++ str, resolved_tail)
DynamicReference tail_str ->
let
reference_location =
BigInt.add head_length (BigInt.fromInt <| (String.length resolved_tail) // 2)
|> Debug.log "reference_location"
_ = Debug.log "resolved_head" resolved_head
_ = Debug.log "resolved_tail" resolved_tail
in
(resolved_head ++ (unsafeBigIntToHexStr reference_location), resolved_tail ++ tail_str)
-- resolveDynamicReferences ( head, tail ) =
-- let
-- -- Number of bytes in the head
-- head_length =
-- bytesLength head
-- in
-- Debug.crash "TODO"
-- resolveDynamicReference elem (acc_head, acc_tail) head_length tail =
-- case elem of
-- Normal hexstr ->
-- resolveDynamicReference elem (acc_head ++ hexstr, acc_tail) head_length tail
-- DynamicReference ->
bytesLength : List EncodedValue -> BigInt
bytesLength encoded_values =
let
elemLength elem =
case elem of
Normal str ->
BigInt.fromInt <| (String.length str) // 2
DynamicReference tail ->
BigInt.fromInt 32
bigIntSum =
List.foldl BigInt.add (BigInt.fromInt 0)
in
encoded_values
|> List.map elemLength
|> bigIntSum
|> Debug.log "bytesLength"
-- partialEncode : Encoder -> List EncodedValue -> List EncodedValue
-- partialEncode encoder encoded_values =
-- encoder encoded_values
{-| Runs two encoders one after the other.
Note that `enc_a |> append enc_b |> append enc_c` will give you
the result of appending enc_c and then enc_b and then enc_a.
Usually, though, don't use this function but instead use `tuple`, `dynamic_array` or `array`.
-}
append : Encoder -> Encoder -> Encoder
append enc_a enc_b =
enc_a ++ enc_b
-- foo : (c -> (a, b)) -> Encoder a -> Encoder b -> Encoder c
-- foo fun encodera encoderb = contramap (,) (encodera >> encoderb)
-- contramap2 : (c -> (a, b)) -> Encoder a -> Encoder b -> Encoder c
-- contramap2 fun enc_a enc_b =
-- let
-- combine_results ((a_head, a_tail), (b_head, b_tail)) = (a_head ++ b_head, a_tail ++ b_tail)
-- in_order (a, b) hexstr_tuple = \val val2 ->
-- (a val hexstr_tuple)
-- |> (b val hexstr_tuple2)
-- in
-- \val hexstr ->
-- val
-- |> fun
-- |> Tuple2.mapFirst (\val -> enc_a val hexstr)
-- |> combine_results
uint256 : UInt256 -> Encoder
uint256 integer =
integer
|> EthABI.Types.UInt.toBigInt
|> unsafeBigInt
int256 : Int256 -> Encoder
int256 integer =
let
bigint =
EthABI.Types.Int.toBigInt integer
twosComplementPow =
BigInt.pow (BigInt.fromInt 2) (BigInt.fromInt 255)
twos_complement =
if BigInt.gte bigint (BigInt.fromInt 0) then
bigint
else
BigInt.add (BigInt.mul twosComplementPow (BigInt.fromInt 2)) (bigint)
in
unsafeBigInt twos_complement
bool : Bool -> Encoder
bool boolean =
if boolean then
unsafeInt 1
else
unsafeInt 0
bytes32 : Bytes32 -> Encoder
bytes32 bytes =
let
head =
bytes
|> EthABI.Types.Bytes32.toString
|> padRightTo32Bytes '0'
in
[ Normal head ]
tuple : List Encoder -> Encoder
tuple encoders =
let
tuple_body =
tupleBody encoders
in
if isDynamicType tuple_body then
[ DynamicReference (resolveDynamicReferences tuple_body) ]
else
tuple_body
-- \( encoded_values, hexstr_tail ) ->
-- case tupleBody encoders of
-- ( only_head, [] ) ->
-- -- Static contents, so static tuple
-- ( encoded_values ++ only_head, hexstr_tail )
-- ( head, tail ) ->
-- -- Dynamic contents, so dynamic tuple
-- ( encoded_values ++ [ DynamicReference "TODO, call resolveReferences here recursively"], hexstr_tail)
isDynamicElem : EncodedValue -> Bool
isDynamicElem elem =
case elem of
DynamicReference _ ->
True
_ ->
False
isDynamicType =
List.any isDynamicElem
{-| An array of as of a non-statically declared size.
TODO distinguish 'variable length' arrays from 'dynamic' datatypes.
Current naming might be confusing.
-}
dynamic_array : (a -> Encoder) -> List a -> Encoder
dynamic_array encoder_fun array =
let
dynamic_array_body =
dynamicArrayBody encoder_fun array
-- concatTuple ( a, b ) =
-- a ++ b
in
-- \( encoded_values, hexstr_tail ) ->
[ DynamicReference (dynamic_array_body)
{- (concatTuple dynamic_array_body) -}
]
{-| An array with a static length.
It will be a static type if all contents are static,
and a dynamic type if it contains a dynamic type.
-}
array : Int -> (a -> Encoder) -> List a -> Encoder
array length encoder_fun array =
let
array_body =
arrayBody encoder_fun array
concatTuple ( a, b ) =
a ++ b
in
if isDynamicType array_body then
[ DynamicReference (resolveDynamicReferences array_body) ]
else
array_body
bytes : Bytes -> Encoder
bytes (Bytes bstr) =
let
bytes_length = String.length bstr
padded_bytestring = padRightToNearestMultipleOf32Bytes '0' bstr
encoded_length = unsafeIntToHexStr bytes_length
in
[DynamicReference (encoded_length ++ padded_bytestring)]
string : String -> Encoder
string str =
str
|> EthABI.Types.Bytes.fromString
|> bytes
-- Internal helper functions:
tupleBody : List Encoder -> Encoder
tupleBody encoders =
List.concat encoders
-- List.foldr append identity encoders
dynamicArrayBody : (a -> Encoder) -> List a -> String
dynamicArrayBody encoding_fun array =
let
encodedLength =
array
|> List.length
|> unsafeIntToHexStr
in
encodedLength ++ resolveDynamicReferences (arrayBody encoding_fun array)
{-| Used by both static and dynamic arrays to encode the elements of the array
-}
arrayBody : (a -> Encoder) -> List a -> Encoder
arrayBody encoding_fun array =
array
|> List.map encoding_fun
|> tupleBody
unsafeInt : Int -> Encoder
unsafeInt int =
int
|> BigInt.fromInt
|> unsafeBigInt
unsafeIntToHexStr int =
int
|> BigInt.fromInt
|> unsafeBigIntToHexStr
elementSize : Hexstring -> Int
elementSize hexstr =
(String.length hexstr) // 64
{-| Unsafe!
Only call after making sure that BigInt is expressible in Int256 resp Uint256!
(This function exists for the overlap in functionality between int256 and uint256 and bool)
-}
unsafeBigInt : BigInt -> Encoder
unsafeBigInt bigint =
let
head =
bigint
|> BigInt.toHexString
|> padLeftTo32Bytes '0'
in
[ Normal head ]
unsafeBigIntToHexStr bigint =
bigint
|> BigInt.toHexString
|> padLeftTo32Bytes '0'
padLeftTo32Bytes : Char -> String -> String
padLeftTo32Bytes char str =
String.padLeft 64 char str
padRightToNearestMultipleOf32Bytes : Char -> String -> String
padRightToNearestMultipleOf32Bytes char str =
let
string_length = String.length str
pad_amount = string_length + (64 - (string_length % 64))
in
String.padRight pad_amount char str
padRightTo32Bytes : Char -> String -> String
padRightTo32Bytes char str =
String.padRight 64 char str
bytes32ToHex : Bytes32 -> String
bytes32ToHex (Bytes32 str) =
str
|> String.toList
|> List.map (Char.toCode >> Hex.toString >> String.padLeft 2 '0')
|> String.join ""
bytes32ToString : String -> Result String String
bytes32ToString bytes =
let
-- two hexchars -> 0..255 -> char
byteToChar =
(Hex.fromString >> Result.map (Char.fromCode))
in
bytes
|> stringGroupsOf 2
|> List.map byteToChar
|> Result.Extra.combine
|> Result.map String.fromList
trimBytesLeft len str =
String.dropLeft (64 - (2 * len)) str
trimBytesRight len str =
String.dropRight (64 - (2 * len)) str
stringGroupsOf : Int -> String -> List String
stringGroupsOf num str =
str
|> String.toList
|> List.Extra.groupsOf num
|> List.map String.fromList