From 544cef72e4302fa826f7f8736723d1ec7d2bd781 Mon Sep 17 00:00:00 2001 From: Olivier Guimbal Date: Thu, 3 Mar 2022 18:14:47 +0100 Subject: [PATCH] Refactor abi encoding --- src/Eth/Abi/Decode.elm | 1 + src/Eth/Abi/Encode.elm | 825 +++++++++++++--------------------------- tests/DecodeAbi.elm | 20 +- tests/DecodeAbiBeta.elm | 2 +- tests/EncodeAbi.elm | 395 +++++++++++++++++-- 5 files changed, 637 insertions(+), 606 deletions(-) diff --git a/src/Eth/Abi/Decode.elm b/src/Eth/Abi/Decode.elm index c015ff4..4069cfd 100644 --- a/src/Eth/Abi/Decode.elm +++ b/src/Eth/Abi/Decode.elm @@ -4,6 +4,7 @@ module Eth.Abi.Decode exposing , staticArray, dynamicArray , abiDecode, andMap, toElmDecoder, fromString , topic, data + , struct ) {-| Decode RPC Responses diff --git a/src/Eth/Abi/Encode.elm b/src/Eth/Abi/Encode.elm index 82c0fc9..730202d 100644 --- a/src/Eth/Abi/Encode.elm +++ b/src/Eth/Abi/Encode.elm @@ -2,8 +2,9 @@ module Eth.Abi.Encode exposing ( Encoding(..), functionCall , uint, int, staticBytes , string, list, bytes - , address, bool, custom + , address, bool , abiEncode, abiEncodeList, stringToHex + , staticList, tuple ) {-| Encode before sending RPC Calls @@ -33,50 +34,20 @@ module Eth.Abi.Encode exposing -} import BigInt exposing (BigInt) +import Dict exposing (Dict) import Eth.Abi.Int as AbiInt import Eth.Types exposing (Address, Hex) -import Eth.Utils exposing (leftPadTo64, remove0x) +import Eth.Utils exposing (leftPadTo64, remove0x, unsafeToHex) import Hex import Internal.Types as Internal import String.UTF8 as UTF8 {-| -} -type Encoding - = AddressE Address - | UintE BigInt - | IntE BigInt - | BoolE Bool - | DBytesE Hex - | BytesE Hex - | StringE String - | ListE (List Encoding) - | CustomE String - - -{-| -} -functionCall : String -> List Encoding -> Hex -functionCall abiSig encodings = - let - byteCodeEncodings = - List.map lowLevelEncode encodings - |> lowLevelEncodeList - in - Internal.Hex (remove0x abiSig ++ byteCodeEncodings) - - -functionCallWithDebug : Internal.DebugLogger String -> String -> List Encoding -> Hex -functionCallWithDebug logger sig encodings = - let - byteCodeEncodings = - List.map lowLevelEncode encodings - |> lowLevelEncodeList - - data = - (remove0x sig ++ byteCodeEncodings) - |> logger "Abi.Encode : " - in - Internal.Hex data +functionCall : String -> List Encoding -> Result String Hex +functionCall abiSig args = + abiEncodeList_ args + |> Result.map (\calldata -> Internal.Hex (remove0x abiSig ++ calldata)) @@ -86,430 +57,73 @@ functionCallWithDebug logger sig encodings = {-| -} uint : BigInt -> Encoding uint = - UintE + BigInt.toHexString >> leftPadTo64 >> EValue {-| -} int : BigInt -> Encoding int = - IntE + AbiInt.toString >> leftPadTo64 >> EValue {-| -} address : Address -> Encoding -address = - AddressE - - - --- {-| -} --- uint8 : BigInt -> Result String Encoding --- uint8 = --- uint 8 --- {-| -} --- uint16 : BigInt -> Result String Encoding --- uint16 = --- uint 16 --- {-| -} --- uint24 : BigInt -> Result String Encoding --- uint24 = --- uint 24 --- {-| -} --- uint32 : BigInt -> Result String Encoding --- uint32 = --- uint 32 --- {-| -} --- uint40 : BigInt -> Result String Encoding --- uint40 = --- uint 40 --- {-| -} --- uint48 : BigInt -> Result String Encoding --- uint48 = --- uint 48 --- {-| -} --- uint56 : BigInt -> Result String Encoding --- uint56 = --- uint 56 --- {-| -} --- uint64 : BigInt -> Result String Encoding --- uint64 = --- uint 64 --- {-| -} --- uint72 : BigInt -> Result String Encoding --- uint72 = --- uint 72 --- {-| -} --- uint80 : BigInt -> Result String Encoding --- uint80 = --- uint 80 --- {-| -} --- uint88 : BigInt -> Result String Encoding --- uint88 = --- uint 88 --- {-| -} --- uint96 : BigInt -> Result String Encoding --- uint96 = --- uint 96 --- {-| -} --- uint104 : BigInt -> Result String Encoding --- uint104 = --- uint 104 --- {-| -} --- uint112 : BigInt -> Result String Encoding --- uint112 = --- uint 112 --- {-| -} --- uint120 : BigInt -> Result String Encoding --- uint120 = --- uint 120 --- {-| -} --- uint128 : BigInt -> Result String Encoding --- uint128 = --- uint 128 --- {-| -} --- uint136 : BigInt -> Result String Encoding --- uint136 = --- uint 136 --- {-| -} --- uint144 : BigInt -> Result String Encoding --- uint144 = --- uint 144 --- {-| -} --- uint152 : BigInt -> Result String Encoding --- uint152 = --- uint 152 --- {-| -} --- uint160 : BigInt -> Result String Encoding --- uint160 = --- uint 160 --- {-| -} --- uint168 : BigInt -> Result String Encoding --- uint168 = --- uint 168 --- {-| -} --- uint176 : BigInt -> Result String Encoding --- uint176 = --- uint 176 --- {-| -} --- uint184 : BigInt -> Result String Encoding --- uint184 = --- uint 184 --- {-| -} --- uint192 : BigInt -> Result String Encoding --- uint192 = --- uint 192 --- {-| -} --- uint200 : BigInt -> Result String Encoding --- uint200 = --- uint 200 --- {-| -} --- uint208 : BigInt -> Result String Encoding --- uint208 = --- uint 208 --- {-| -} --- uint216 : BigInt -> Result String Encoding --- uint216 = --- uint 216 --- {-| -} --- uint224 : BigInt -> Result String Encoding --- uint224 = --- uint 224 --- {-| -} --- uint232 : BigInt -> Result String Encoding --- uint232 = --- uint 232 --- {-| -} --- uint240 : BigInt -> Result String Encoding --- uint240 = --- uint 240 --- {-| -} --- uint248 : BigInt -> Result String Encoding --- uint248 = --- uint 248 --- {-| -} --- uint256 : BigInt -> Result String Encoding --- uint256 = --- uint 256 --- {-| -} --- int8 : BigInt -> Result String Encoding --- int8 = --- int 8 --- {-| -} --- int16 : BigInt -> Result String Encoding --- int16 = --- int 16 --- {-| -} --- int24 : BigInt -> Result String Encoding --- int24 = --- int 24 --- {-| -} --- int32 : BigInt -> Result String Encoding --- int32 = --- int 32 --- {-| -} --- int40 : BigInt -> Result String Encoding --- int40 = --- int 40 --- {-| -} --- int48 : BigInt -> Result String Encoding --- int48 = --- int 48 --- {-| -} --- int56 : BigInt -> Result String Encoding --- int56 = --- int 56 --- {-| -} --- int64 : BigInt -> Result String Encoding --- int64 = --- int 64 --- {-| -} --- int72 : BigInt -> Result String Encoding --- int72 = --- int 72 --- {-| -} --- int80 : BigInt -> Result String Encoding --- int80 = --- int 80 --- {-| -} --- int88 : BigInt -> Result String Encoding --- int88 = --- int 88 --- {-| -} --- int96 : BigInt -> Result String Encoding --- int96 = --- int 96 --- {-| -} --- int104 : BigInt -> Result String Encoding --- int104 = --- int 104 --- {-| -} --- int112 : BigInt -> Result String Encoding --- int112 = --- int 112 --- {-| -} --- int120 : BigInt -> Result String Encoding --- int120 = --- int 120 --- {-| -} --- int128 : BigInt -> Result String Encoding --- int128 = --- int 128 --- {-| -} --- int136 : BigInt -> Result String Encoding --- int136 = --- int 136 --- {-| -} --- int144 : BigInt -> Result String Encoding --- int144 = --- int 144 --- {-| -} --- int152 : BigInt -> Result String Encoding --- int152 = --- int 152 --- {-| -} --- int160 : BigInt -> Result String Encoding --- int160 = --- int 160 --- {-| -} --- int168 : BigInt -> Result String Encoding --- int168 = --- int 168 --- {-| -} --- int176 : BigInt -> Result String Encoding --- int176 = --- int 176 --- {-| -} --- int184 : BigInt -> Result String Encoding --- int184 = --- int 184 --- {-| -} --- int192 : BigInt -> Result String Encoding --- int192 = --- int 192 --- {-| -} --- int200 : BigInt -> Result String Encoding --- int200 = --- int 200 --- {-| -} --- int208 : BigInt -> Result String Encoding --- int208 = --- int 208 --- {-| -} --- int216 : BigInt -> Result String Encoding --- int216 = --- int 216 --- {-| -} --- int224 : BigInt -> Result String Encoding --- int224 = --- int 224 --- {-| -} --- int232 : BigInt -> Result String Encoding --- int232 = --- int 232 --- {-| -} --- int240 : BigInt -> Result String Encoding --- int240 = --- int 240 --- {-| -} --- int248 : BigInt -> Result String Encoding --- int248 = --- int 248 --- {-| -} --- int256 : BigInt -> Result String Encoding --- int256 = --- int 256 --- {-| -} --- bytes1 : Hex -> Result String Encoding --- bytes1 = --- staticBytes 1 --- {-| -} --- bytes2 : Hex -> Result String Encoding --- bytes2 = --- staticBytes 2 --- {-| -} --- bytes3 : Hex -> Result String Encoding --- bytes3 = --- staticBytes 3 --- {-| -} --- bytes4 : Hex -> Result String Encoding --- bytes4 = --- staticBytes 4 --- {-| -} --- bytes5 : Hex -> Result String Encoding --- bytes5 = --- staticBytes 5 --- {-| -} --- bytes6 : Hex -> Result String Encoding --- bytes6 = --- staticBytes 6 --- {-| -} --- bytes7 : Hex -> Result String Encoding --- bytes7 = --- staticBytes 7 --- {-| -} --- bytes8 : Hex -> Result String Encoding --- bytes8 = --- staticBytes 8 --- {-| -} --- bytes9 : Hex -> Result String Encoding --- bytes9 = --- staticBytes 9 --- {-| -} --- bytes10 : Hex -> Result String Encoding --- bytes10 = --- staticBytes 10 --- {-| -} --- bytes11 : Hex -> Result String Encoding --- bytes11 = --- staticBytes 11 --- {-| -} --- bytes12 : Hex -> Result String Encoding --- bytes12 = --- staticBytes 12 --- {-| -} --- bytes13 : Hex -> Result String Encoding --- bytes13 = --- staticBytes 13 --- {-| -} --- bytes14 : Hex -> Result String Encoding --- bytes14 = --- staticBytes 14 --- {-| -} --- bytes15 : Hex -> Result String Encoding --- bytes15 = --- staticBytes 15 --- {-| -} --- bytes16 : Hex -> Result String Encoding --- bytes16 = --- staticBytes 16 --- {-| -} --- bytes17 : Hex -> Result String Encoding --- bytes17 = --- staticBytes 17 --- {-| -} --- bytes18 : Hex -> Result String Encoding --- bytes18 = --- staticBytes 18 --- {-| -} --- bytes19 : Hex -> Result String Encoding --- bytes19 = --- staticBytes 19 --- {-| -} --- bytes20 : Hex -> Result String Encoding --- bytes20 = --- staticBytes 20 --- {-| -} --- bytes21 : Hex -> Result String Encoding --- bytes21 = --- staticBytes 21 --- {-| -} --- bytes22 : Hex -> Result String Encoding --- bytes22 = --- staticBytes 22 --- {-| -} --- bytes23 : Hex -> Result String Encoding --- bytes23 = --- staticBytes 23 --- {-| -} --- bytes24 : Hex -> Result String Encoding --- bytes24 = --- staticBytes 24 --- {-| -} --- bytes25 : Hex -> Result String Encoding --- bytes25 = --- staticBytes 25 --- {-| -} --- bytes26 : Hex -> Result String Encoding --- bytes26 = --- staticBytes 26 --- {-| -} --- bytes27 : Hex -> Result String Encoding --- bytes27 = --- staticBytes 27 --- {-| -} --- bytes28 : Hex -> Result String Encoding --- bytes28 = --- staticBytes 28 --- {-| -} --- bytes29 : Hex -> Result String Encoding --- bytes29 = --- staticBytes 29 --- {-| -} --- bytes30 : Hex -> Result String Encoding --- bytes30 = --- staticBytes 30 --- {-| -} --- bytes31 : Hex -> Result String Encoding --- bytes31 = --- staticBytes 31 --- {-| -} --- bytes32 : Hex -> Result String Encoding --- bytes32 = --- staticBytes 32 +address (Internal.Address addr) = + addr |> leftPadTo64 |> EValue {-| -} bool : Bool -> Encoding -bool = - BoolE +bool v = + (if v then + "1" + + else + "0" + ) + |> leftPadTo64 + |> EValue -{-| -} -staticBytes : Int -> Hex -> Encoding -staticBytes size = - BytesE +{-| Encodes inline bytes (fixed size byte array) +-} +staticBytes : Hex -> Encoding +staticBytes (Internal.Hex hex) = + hex |> remove0x |> EValue -{-| -} +{-| Creates a pointer to a byte array +-} bytes : Hex -> Encoding bytes = - DBytesE + EDynamicBytes >> EPointerTo -{-| -} +{-| Inline list (fixed size) +-} +staticList : List Encoding -> Encoding +staticList = + EInline + + +tuple : List Encoding -> Encoding +tuple props = + -- Tuples are stored as a pointer when any of their members are pointers + -- but as inlines when all members are fixed-sized + -- Why ? 🤷‍♂️ No idea... (their dynamic members could be stored as a fixed-size pointer, removing the need for a pointer to the structure body itself) + -- see js implem here https://github.com/ethers-io/ethers.js/blob/master/packages/abi/src.ts/coders/tuple.ts + if List.any isDynamic props then + EPointerTo (EInline props) + + else + EInline props + + +{-| Dynamic list +-} list : List Encoding -> Encoding list = - ListE + EDynamicList @@ -519,177 +133,248 @@ list = {-| -} string : String -> Encoding string = - StringE + stringToHex >> unsafeToHex >> EDynamicBytes >> EPointerTo {-| -} -custom : String -> Encoding -custom = - CustomE +abiEncode : Encoding -> Result String Hex +abiEncode e = + [ e ] |> abiEncodeList +{-| -} +abiEncodeList : List Encoding -> Result String Hex +abiEncodeList = + abiEncodeList_ >> Result.map Internal.Hex --- Low Level +abiEncodeList_ : List Encoding -> Result String String +abiEncodeList_ data = + computeLayout 2 [ { id = 1, offset = 0, data = data } ] + |> layoutToHex -{-| -} -abiEncode : Encoding -> Hex -abiEncode = - lowLevelEncode >> (\v -> lowLevelEncodeList [ v ]) >> Internal.Hex +type Encoding + = -- A 32 bytes value (or shorter, which will be padded with zeros) + EValue String + -- Will write dynamic bytes (32 bytes for length, then data) in place + | EDynamicBytes Hex + -- Will write a dynamic list (32 bytes for length, then data) in place + | EDynamicList (List Encoding) + -- Write multiple elements, one after another, in place + | EInline (List Encoding) + -- Write a pointer to a value, and defers the writing of those values actual bodies + | EPointerTo Encoding -{-| -} -abiEncodeList : List Encoding -> Hex -abiEncodeList = - List.map lowLevelEncode >> lowLevelEncodeList >> Internal.Hex - - - --- Internal --- {-| Low level uint helper --- -} --- uint : Int -> BigInt -> Result String Encoding --- uint size num = --- if modBy 8 size /= 0 || size <= 0 || size > 256 then --- Err <| "Invalid size: " ++ String.fromInt size --- else if BigInt.lt num (BigInt.pow (BigInt.fromInt 2) (BigInt.fromInt size)) then --- -- TODO Figure out if it's 2^n or (2^n - 1), e.g. uint8 should not be over 255 or 256 ? --- Ok <| UintE num --- else --- Err <| --- "Uint overflow: " --- ++ BigInt.toString num --- ++ " is larger than uint" --- ++ String.fromInt size --- {-| Low level int helper --- -} --- int : Int -> BigInt -> Result String Encoding --- int size num = --- if modBy 8 size /= 0 || size <= 0 || size > 256 then --- Err <| "Invalid size: " ++ String.fromInt size --- else if ... then --- -- TODO Figure out if int8 should not be over 127 or 128 ? --- Ok <| IntE num --- else --- -- Account for overflow and underflow --- Err <| --- "Int overflow/underflow: " --- ++ BigInt.toString num --- ++ " is larger than uint" --- ++ String.fromInt size - - -{-| (Maybe (Size of Dynamic Value), Value) --} -type alias LowLevelEncoding = - ( Maybe Int, String ) +type DataItem + = -- a value, with an id + DValue String -- value + | DPointerTo Int Int -- pointer to a given block ID, its origin block ID -{-| -} -toStaticLLEncoding : String -> LowLevelEncoding -toStaticLLEncoding strVal = - ( Nothing - , leftPadTo64 strVal - ) -bytesOrStringToDynamicLLEncoding : String -> LowLevelEncoding -bytesOrStringToDynamicLLEncoding strVal = - ( Just <| String.length strVal // 2 - , rightPadMod64 strVal - ) +-- Block id, block header size (pointer address offset), and data -listToDynamicLLEncoding : Int -> String -> LowLevelEncoding -listToDynamicLLEncoding listLen strVal = - ( Just listLen - , rightPadMod64 strVal - ) +type alias DataBlock = + { id : Int, offset : Int, data : List DataItem } -{-| -} -lowLevelEncode : Encoding -> LowLevelEncoding -lowLevelEncode enc = - case enc of - AddressE (Internal.Address address_) -> - toStaticLLEncoding address_ +type alias BlockToLayout = + { id : Int + , offset : Int + , data : List Encoding + } + + +computeLayout : Int -> List BlockToLayout -> List DataBlock +computeLayout cnt toLayout = + case toLayout of + [] -> + [] - UintE uint_ -> - BigInt.toHexString uint_ - |> toStaticLLEncoding + b :: bs -> + let + -- compute this block's layout, and get back some queued blocks + ( newCnt, blockLayout, queuedBlocks ) = + computeOneLayout b.id b.data cnt - IntE int_ -> - AbiInt.toString int_ - |> toStaticLLEncoding + block = + { id = b.id, offset = b.offset, data = blockLayout } + in + block :: computeLayout newCnt (queuedBlocks ++ bs) - BoolE True -> - toStaticLLEncoding "1" - BoolE False -> - toStaticLLEncoding "0" +computeOneLayout : Int -> List Encoding -> Int -> ( Int, List DataItem, List BlockToLayout ) +computeOneLayout blockId toLayout cnt = + case toLayout of + [] -> + ( cnt, [], [] ) - DBytesE (Internal.Hex hexString) -> - bytesOrStringToDynamicLLEncoding hexString + i :: is -> + case i of + EValue x -> + ---------- simple values are stored as it, grouped 64-by-64 (i.e. 256 bits-by-256 bits) + let + -- build next values + ( newCnt, nexts, queue ) = + computeOneLayout blockId is cnt - BytesE (Internal.Hex hexString) -> - remove0x hexString - |> toStaticLLEncoding + -- all values must be a multiple of 64 + paddedValue = + rightPadMod64 x - StringE string_ -> - stringToHex string_ - |> bytesOrStringToDynamicLLEncoding + -- build this value + this = + DValue paddedValue + in + ( newCnt, this :: nexts, queue ) - ListE encodings -> - abiEncodeList encodings - |> (\(Internal.Hex hexString) -> - listToDynamicLLEncoding (List.length encodings) - hexString - ) + EPointerTo toData -> + ---------- pointers are stored as a pointer to the inner value + let + pointedId = + cnt - CustomE string_ -> - remove0x string_ - |> toStaticLLEncoding + -- build next values + ( newCnt, nexts, queue ) = + computeOneLayout blockId is (cnt + 1) + -- build this value + this = + DPointerTo pointedId blockId -lowLevelEncodeList : List LowLevelEncoding -> String -lowLevelEncodeList vals = - let - reducer : LowLevelEncoding -> ( Int, String, String ) -> ( Int, String, String ) - reducer ( mLength, val ) ( dynValPointer, staticVals, dynamicVals ) = - case mLength of - Just length -> + dataBlock : BlockToLayout + dataBlock = + { id = pointedId, offset = 0, data = [ toData ] } + in + -- build the pointer + ( newCnt, this :: nexts, dataBlock :: queue ) + + EDynamicBytes (Internal.Hex hex) -> let - newDynValPointer = - dynValPointer + 32 + (String.length val // 2) + -- bits (*4) ? or bytes (//2) ? + bytesLen = + (String.length hex // 2) + |> BigInt.fromInt + in + computeOneLayout blockId (uint bytesLen :: EValue hex :: is) cnt - newStaticVals = - Hex.toString dynValPointer - |> leftPadTo64 + EDynamicList listVals -> + ---------- dynamic lists are stored as a pointer to the array length (which wil lbe followed by list elements) somewhere in encoded value + let + -- encode array elements on a new stack + listLen = + List.length listVals |> BigInt.fromInt |> uint - newDynVals = - Hex.toString length - |> leftPadTo64 - |> (\lengthInHex -> lengthInHex ++ val) + -- build next values + ( newCnt, nexts, queue ) = + computeOneLayout blockId is (cnt + 1) + + -- build this value + this = + DPointerTo cnt blockId + + listBody : BlockToLayout + listBody = + { id = cnt, offset = 32, data = listLen :: listVals } in - ( newDynValPointer - -- newPointer - = previousPointer + (length of hexLengthWord) + (length of val words) - , staticVals ++ newStaticVals - , dynamicVals ++ newDynVals - ) + -- build the pointer + ( newCnt, this :: nexts, listBody :: queue ) + + EInline vals -> + ---------- Consecutive inline elements + computeOneLayout blockId (vals ++ is) cnt - Nothing -> - ( dynValPointer - , staticVals ++ val - , dynamicVals + +measureLayout : List DataBlock -> ( List DataItem, Dict Int ( Int, Int ) ) +measureLayout blocks = + let + ( _, lst, retPos ) = + blocks + |> List.foldl + (\{ id, offset, data } ( thisPos, prev, posById ) -> + let + newPos = + thisPos + measureBlock data + in + ( newPos, prev ++ data, Dict.insert id ( thisPos, thisPos + offset ) posById ) ) + ( 0, [], Dict.empty ) in - List.foldl reducer ( List.length vals * 32, "", "" ) vals - |> (\( _, sVals, dVals ) -> sVals ++ dVals) + ( lst, retPos ) + + +measureBlock : List DataItem -> Int +measureBlock = + List.foldl + (\val acc -> + case val of + DValue str -> + acc + String.length str // 2 + + DPointerTo _ _ -> + acc + 32 + ) + 0 + + +layoutToHex : List DataBlock -> Result String String +layoutToHex stack = + let + _ = + Debug.log "Layout to hex" stack + + ( lst, pos ) = + measureLayout stack + in + lst + |> List.foldl + (\v acc -> + case acc of + Err e -> + Err e + + Ok prev -> + case v of + DValue str -> + Ok (str :: prev) + + DPointerTo to from -> + case ( Dict.get to pos, Dict.get from pos ) of + ( Just ( toPos, _ ), Just ( _, fromPos ) ) -> + let + encodedPtr = + (toPos - fromPos) |> BigInt.fromInt |> BigInt.toHexString |> leftPadTo64 + in + Ok (encodedPtr :: prev) + + _ -> + Err <| "LayoutToHex: Pointer not found" + ) + (Ok []) + |> Result.map List.reverse + |> Result.map (String.join "") + + +{-| Function that takes a list of values to encode, the current ID of the first value to encode, +and which returns the next avaialble ID and the flatten layout of encoded values (with their ids) +-} +isDynamic : Encoding -> Bool +isDynamic e = + case e of + EDynamicBytes _ -> + True + EDynamicList _ -> + True + EPointerTo _ -> + True --- Helpers --- Move to utils + _ -> + False {-| Right pads a string with "0"'s till (strLength % 64 == 0) diff --git a/tests/DecodeAbi.elm b/tests/DecodeAbi.elm index 904ffb5..f2ddb68 100644 --- a/tests/DecodeAbi.elm +++ b/tests/DecodeAbi.elm @@ -2,10 +2,13 @@ module DecodeAbi exposing (TestVal1, TestVal2, complexStorage, decodeInt, testVa -- import Fuzz exposing (Fuzzer, int, list, string) -import Abi.Decode as Abi import BigInt exposing (BigInt) +import Eth.Abi.Decode as Abi +import Eth.Types exposing (Hex) +import Eth.Utils exposing (hexToString) import Expect import Test exposing (..) +import Eth.Utils exposing (unsafeToHex) @@ -154,13 +157,14 @@ testVal1_elm_data : Result String TestVal1 testVal1_elm_data = let b2Vec = - [ "0x1234", "0x5678", "0xffff", "0x0000" ] + [ "0x1234", "0x5678", "0xffff", "0x0000" ] |> List.map unsafeToHex v3Val = - BigInt.fromString + BigInt.fromIntString "-999999999999999999999999999999999999999999999999999999999999999" |> Result.fromMaybe "Error decoding bigInt in Tests.Abi.makeGetVals" + makeGetVals : BigInt -> TestVal1 makeGetVals bigNegativeInt = { uintVal = BigInt.fromInt 123 , intVal = BigInt.fromInt -128 @@ -180,9 +184,9 @@ testVal1_elm_data = , emptyArray = [] , stringVal = "wtf mate" , emptyString = "" - , bytes16Val = "0x31323334353637383930313233343536" + , bytes16Val = unsafeToHex "0x31323334353637383930313233343536" , bytes2VectorListVal = [ b2Vec, b2Vec, b2Vec ] - , emptyBytes = "0x" + , emptyBytes = unsafeToHex "0x" } in Result.map makeGetVals v3Val @@ -206,9 +210,9 @@ type alias TestVal1 = , emptyArray : List BigInt , stringVal : String , emptyString : String - , bytes16Val : String - , bytes2VectorListVal : List (List String) - , emptyBytes : String + , bytes16Val : Hex + , bytes2VectorListVal : List (List Hex) + , emptyBytes : Hex } diff --git a/tests/DecodeAbiBeta.elm b/tests/DecodeAbiBeta.elm index 8f763a4..6a601e7 100644 --- a/tests/DecodeAbiBeta.elm +++ b/tests/DecodeAbiBeta.elm @@ -2,7 +2,7 @@ module DecodeAbiBeta exposing (arrayOfBytesData) -- import Fuzz exposing (Fuzzer, int, list, string) -import Abi.Decode as Abi +-- import Abi.Decode as Abi import BigInt exposing (BigInt) import Expect import Test exposing (..) diff --git a/tests/EncodeAbi.elm b/tests/EncodeAbi.elm index 96fe633..e49bfbc 100644 --- a/tests/EncodeAbi.elm +++ b/tests/EncodeAbi.elm @@ -1,43 +1,384 @@ -module EncodeAbi exposing (encodeInt) +module EncodeAbi exposing (..) -import Abi.Encode as Abi -import BigInt exposing (BigInt) -import Eth.Utils +import BigInt exposing (fromInt) +import Eth.Abi.Encode as E +import Eth.Types exposing (Address, Hex) +import Eth.Utils exposing (hexToString, remove0x, unsafeToAddress, unsafeToHex) import Expect +import String exposing (join) +import String.Extra exposing (wrapWith) import Test exposing (..) +{-| ====> ALL TESTS CASES BELOW HAVE BEEN GENERATED FROM ethersjs: +import \* as ethers from 'ethers'; +const coder = new ethers.utils.AbiCoder(); +function print(x: string) { +if (x.startsWith('0x')) { +x = x.substr(2); +} +let v = ''; +while (x.length) { +v += x.substr(0, 64) + '", +"'; +x = x.substring(64); +} +console.log(v); +} --- Abi Encoders +// => see test cases +-} +uint : Int -> E.Encoding +uint = + BigInt.fromInt >> E.uint -encodeInt : Test -encodeInt = - describe "Int Encoding" - [ test "-120" <| - \_ -> - Abi.abiEncode (Abi.int <| BigInt.fromInt -120) - |> Eth.Utils.hexToString - |> Expect.equal "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff88" - , test "120" <| - \_ -> - Abi.abiEncode (Abi.int <| BigInt.fromInt 120) - |> Eth.Utils.hexToString - |> Expect.equal "0x0000000000000000000000000000000000000000000000000000000000000078" - , test "max positive int256" <| - \_ -> - BigInt.fromString "57896044618658097711785492504343953926634992332820282019728792003956564819967" - |> Maybe.map (Abi.int >> Abi.abiEncode >> Eth.Utils.hexToString) - |> Expect.equal (Just "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - , test "max negative int256" <| + +pointers : Test +pointers = + describe "Pointers encoding" <| + [ test "ok" (\_ -> Expect.pass) + + -- , test "Encode simple layout" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint', 'uint'], + -- [ + -- 1, 2 + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000002" + -- ] + -- encoded = + -- E.abiEncodeList + -- [ uint 1 + -- , uint 2 + -- ] + -- in + -- expectHex exp encoded + -- , test "Encode simple with array 1" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint[]'], + -- [ + -- [2] + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000020" + -- , "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000002" + -- ] + -- encoded = + -- E.abiEncodeList + -- [ E.list [ uint 2 ] + -- ] + -- in + -- expectHex exp encoded + -- , test "Encode simple with array 2" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint', 'uint[]'], + -- [ + -- 1, [2] + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000040" + -- , "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000002" + -- ] + -- encoded = + -- E.abiEncodeList + -- [ uint 1 + -- , E.list [ uint 2 ] + -- ] + -- in + -- expectHex exp encoded + -- , test "Encode simple with array 3" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint', 'uint[]', 'uint'], + -- [ + -- 1, [2], 3 + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000060" + -- , "0000000000000000000000000000000000000000000000000000000000000003" + -- , "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000002" + -- ] + -- encoded = + -- E.abiEncodeList + -- [ uint 1 + -- , E.list [ uint 2 ] + -- , uint 3 + -- ] + -- in + -- expectHex exp encoded + -- , test "Encode string array" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint', 'uint'], + -- [ + -- 1, 2 + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000001" + -- , "0000000000000000000000000000000000000000000000000000000000000002" + -- ] + -- encoded = + -- E.abiEncodeList + -- [ uint 1 + -- , uint 2 + -- ] + -- in + -- expectHex exp encoded + -- , test "Encode muliple inline tuples in list" <| + -- \_ -> + -- let + -- {- + -- print(coder.encode( + -- ['uint', 'tuple(uint,uint)[]', 'uint'], + -- [ + -- 1, [[1,2], [3,4]], 5 + -- ] + -- )) + -- -} + -- exp = + -- [ "0000000000000000000000000000000000000000000000000000000000000001" -- 1 + -- , "0000000000000000000000000000000000000000000000000000000000000060" -- pointer to array + -- , "0000000000000000000000000000000000000000000000000000000000000005" -- 5 + -- -- array body + -- , "0000000000000000000000000000000000000000000000000000000000000002" -- array len + -- , "0000000000000000000000000000000000000000000000000000000000000001" -- elt 1 (part 1) + -- , "0000000000000000000000000000000000000000000000000000000000000002" -- elt 1 (part 2) + -- , "0000000000000000000000000000000000000000000000000000000000000003" -- elt 2 (part 1) + -- , "0000000000000000000000000000000000000000000000000000000000000004" -- elt 2 (part 2) + -- ] + -- encoded = + -- E.abiEncodeList + -- [ uint 1 + -- , E.list + -- [ E.tuple [ uint 1, uint 2 ] + -- , E.tuple [ uint 3, uint 4 ] + -- ] + -- , uint 5 + -- ] + -- in + -- expectHex exp encoded + , test "Encode muliple complex tuples in list" <| \_ -> - BigInt.fromString "-57896044618658097711785492504343953926634992332820282019728792003956564819968" - |> Maybe.map (Abi.int >> Abi.abiEncode >> Eth.Utils.hexToString) - |> Expect.equal (Just "0x8000000000000000000000000000000000000000000000000000000000000000") + let + {- + print(coder.encode( + ['uint', 'tuple(uint,string)[]', 'uint'], + [ + 1, [[2,'some data'], [3,'other data']], 4 + ] + )) + -} + exp = + [ "0000000000000000000000000000000000000000000000000000000000000001" -- 1 + , "0000000000000000000000000000000000000000000000000000000000000060" -- pointer to array + , "0000000000000000000000000000000000000000000000000000000000000004" -- 4 + + -- array body + , "0000000000000000000000000000000000000000000000000000000000000002" -- array len + , "0000000000000000000000000000000000000000000000000000000000000040" -- pointer to struct 1 + , "00000000000000000000000000000000000000000000000000000000000000c0" -- pointer to struct 2 + + -- struct 1 body + , "0000000000000000000000000000000000000000000000000000000000000002" -- 2 + , "0000000000000000000000000000000000000000000000000000000000000040" -- pointer to string 1 + + -- string 1 + , "0000000000000000000000000000000000000000000000000000000000000009" -- string len + , "736f6d6520646174610000000000000000000000000000000000000000000000" -- string data + + -- struct 2 body + , "0000000000000000000000000000000000000000000000000000000000000003" -- 3 + , "0000000000000000000000000000000000000000000000000000000000000040" -- pointer to string 2 + + -- string 2 + , "000000000000000000000000000000000000000000000000000000000000000a" -- string len + , "6f74686572206461746100000000000000000000000000000000000000000000" -- string data + ] + + encoded = + E.abiEncodeList + [ uint 1 + , E.list + [ E.tuple [ uint 2, E.string "some data" ] + , E.tuple [ uint 3, E.string "other data" ] + ] + , uint 4 + ] + in + expectHex exp encoded ] +someCallBody : List SomeStruct -> Result String Hex +someCallBody elts = + let + eltsEncoded = + elts |> List.map encodeSubStruct |> E.list + + someToken = + unsafeToAddress "0x0eb3a705fc54725037cc9e008bdede697f62f335" + in + E.abiEncodeList [ E.uint (fromInt 8), E.address someToken, eltsEncoded ] + + + +-- struct SomeStruct { +-- bytes32 someBytes32Str; +-- address token; +-- bytes callData; +-- bool someBool; +-- } + +type alias SomeStruct = + { someBytes32Str : String + , token : Address + , callData : Hex + , someBool : Bool + } + + +encodeSubStruct : SomeStruct -> E.Encoding +encodeSubStruct o = + let + nameEncoded = + o.someBytes32Str + |> E.stringToHex + |> unsafeToHex + |> E.staticBytes + in + E.tuple [ nameEncoded, E.address o.token, E.bytes o.callData, E.bool o.someBool ] + + + +-- complexStruct : Test +-- complexStruct = +-- describe "Encoding complex struct" +-- [ test "Encode struct with empty array" <| +-- \_ -> +-- let +-- encoded = +-- someCallBody [] +-- expected = +-- [ "0000000000000000000000000000000000000000000000000000000000000008" -- id +-- , "0000000000000000000000000eb3a705fc54725037cc9e008bdede697f62f335" -- out otken +-- , "0000000000000000000000000000000000000000000000000000000000000060" -- array pointer +-- , "0000000000000000000000000000000000000000000000000000000000000000" -- array len +-- ] +-- in +-- Expect.equal (Ok (unsafeToHex <| join "" expected)) encoded +-- , test "Encode struct with array elements" <| +-- \_ -> +-- let +-- encoded = +-- someCallBody +-- [ { someBytes32Str = "ZeroEx" +-- , token = otherToken +-- , callData = unsafeToHex "0x11111111111111111111111111111111111111111111111111111111111111112222" +-- , someBool = True +-- } +-- ] +-- expected = +-- [ "0000000000000000000000000000000000000000000000000000000000000008" -- id +-- , "0000000000000000000000000eb3a705fc54725037cc9e008bdede697f62f335" -- token +-- , "0000000000000000000000000000000000000000000000000000000000000060" -- array pointer +-- , "0000000000000000000000000000000000000000000000000000000000000001" -- array len +-- , "0000000000000000000000000000000000000000000000000000000000000020" -- first elt pointer +-- , "5a65726f45780000000000000000000000000000000000000000000000000000" -- "ZeroEx" +-- , "0000000000000000000000002170ed0880ac9a755fd29b2688956bd959f933f8" -- token +-- , "0000000000000000000000000000000000000000000000000000000000000080" -- pointer to calldata (two lines below) +-- , "0000000000000000000000000000000000000000000000000000000000000001" -- boolean "true" +-- , "0000000000000000000000000000000000000000000000000000000000000022" -- calldata len +-- , "1111111111111111111111111111111111111111111111111111111111111111" -- calldata +-- , "2222000000000000000000000000000000000000000000000000000000000000" -- calldata (part 2) +-- ] +-- in +-- expectHex expected encoded +-- ] + + +coalesce : a -> Maybe a -> a +coalesce a ma = + case ma of + Nothing -> + a + + Just v -> + v + + +expectHex : List String -> Result String Hex -> Expect.Expectation +expectHex expected result = + case result of + Err e -> + Expect.fail e + + Ok hex -> + Expect.equal (wrapWith 64 " " <| join "" expected) (wrapWith 64 " " <| remove0x <| hexToString <| hex) + + +otherToken : Eth.Types.Address +otherToken = + unsafeToAddress "0x2170ed0880ac9a755fd29b2688956bd959f933f8" + + + +-- Abi Encoders +-- encodeInt : Test +-- encodeInt = +-- describe "Int Encoding" +-- [ test "-120" <| +-- \_ -> +-- E.abiEncode (E.int <| BigInt.fromInt -120) +-- |> Result.map Eth.Utils.hexToString +-- |> Expect.equal (Ok "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff88") +-- , test "120" <| +-- \_ -> +-- E.abiEncode (E.int <| BigInt.fromInt 120) +-- |> Result.map Eth.Utils.hexToString +-- |> Expect.equal (Ok "0x0000000000000000000000000000000000000000000000000000000000000078") +-- , test "max positive int256" <| +-- \_ -> +-- BigInt.fromIntString "57896044618658097711785492504343953926634992332820282019728792003956564819967" +-- |> Maybe.map (E.int >> E.abiEncode >> Result.map Eth.Utils.hexToString) +-- |> Expect.equal (Just (Ok "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) +-- , test "max negative int256" <| +-- \_ -> +-- BigInt.fromIntString "-57896044618658097711785492504343953926634992332820282019728792003956564819968" +-- |> Maybe.map (E.int >> E.abiEncode >> Result.map Eth.Utils.hexToString) +-- |> Expect.equal (Just (Ok "0x8000000000000000000000000000000000000000000000000000000000000000")) +-- ] -- encodeComplex : Hex -- encodeComplex = -- let