Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/abi.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ defmodule ABI do
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"

iex> ABI.encode("dynamicUint(uint[])", [[1,2,3,4,5]])
...> |> Base.encode16(case: :lower)
"5d4e03420000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005"

iex> ABI.encode("getVerificationContract(string)", ["test"])
...> |> Base.encode16(case: :lower)
"8575e5a5000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"

iex> ABI.encode("(string)", [{"Ether Token"}])
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"
Expand Down
2 changes: 0 additions & 2 deletions lib/abi/function_selector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,7 @@ defmodule ABI.FunctionSelector do
defp get_type({:ufixed, element_count, precision}), do: "ufixed#{element_count}x#{precision}"
defp get_type({:bytes, size}), do: "bytes#{size}"
defp get_type(:function), do: "function"

defp get_type({:array, type, element_count}), do: "#{get_type(type)}[#{element_count}]"

defp get_type(:bytes), do: "bytes"
defp get_type(:string), do: "string"
defp get_type({:array, type}), do: "#{get_type(type)}[]"
Expand Down
79 changes: 58 additions & 21 deletions lib/abi/type_encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ defmodule ABI.TypeEncoder do
...> |> Base.encode16(case: :lower)
"3d0ec53300000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"

iex> [<<1>>]
...> |> ABI.TypeEncoder.encode(
...> %ABI.FunctionSelector{
...> function: "foo",
...> types: [
...> :bytes
...> ]
...> }
...> )
...> |> Base.encode16(case: :lower)
"30c8d1da000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000"
iex> [[17, 1], true]
...> |> ABI.TypeEncoder.encode(
...> %ABI.FunctionSelector{
Expand All @@ -96,10 +107,28 @@ defmodule ABI.TypeEncoder do
...> )
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000001"

iex> [[]]
...> |> ABI.TypeEncoder.encode(
...> %ABI.FunctionSelector{
...> function: nil,
...> types: [
...> {:array, {:uint, 32}}
...> ]
...> }
...> )
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000000"


"""
@spec encode(list(), ABI.FunctionSelector.t) :: binary()
def encode(data, %ABI.FunctionSelector{function: nil} = function_selector) do
encode_raw(data, function_selector.types)
end
def encode(data, function_selector) do
encode_method_id(function_selector) <>
encode_raw(data, function_selector.types)
encode_head_and_data(data, function_selector.types)
end

@doc """
Expand All @@ -118,6 +147,26 @@ defmodule ABI.TypeEncoder do
do_encode(types, data, [])
end

defp encode_head_and_data(data, types) do
body_start = Enum.count(types) * 32

{head, body, [], _} =
Enum.reduce(
types,
{<<>>, <<>>, data, body_start},
fn type, {head, body, data, body_position} ->
{encoded, rest} = encode_type(type, data)
if ABI.FunctionSelector.is_dynamic?(type) do
# If we're a dynamic type, just add encoded length to the head and encoded value to the body
{head <> encode_uint(body_position, 256), body <> encoded, rest, body_position + byte_size(encoded)}
else
# If we're a static type, simply add encoded value to the head
{head <> encoded, body, rest, body_position}
end
end)
head <> body
end

@spec encode_method_id(%ABI.FunctionSelector{}) :: binary()
defp encode_method_id(%ABI.FunctionSelector{function: nil}), do: ""
defp encode_method_id(function_selector) do
Expand Down Expand Up @@ -177,28 +226,16 @@ defmodule ABI.TypeEncoder do
end

defp encode_type({:tuple, types}, [data|rest]) do
# all head items are 32 bytes in length and there will be exactly
# `count(types)` of them, so the tail starts at `32 * count(types)`.
tail_start = ( types |> Enum.count ) * 32

{head, tail, [], _} = Enum.reduce(types, {<<>>, <<>>, data |> Tuple.to_list, tail_start}, fn type, {head, tail, data, tail_position} ->
{el, rest} = encode_type(type, data)

if ABI.FunctionSelector.is_dynamic?(type) do
# If we're a dynamic type, just encoded the length to head and the element to body
{head <> encode_uint(tail_position, 256), tail <> el, rest, tail_position + byte_size(el)}
else
# If we're a static type, simply encode the el to the head
{head <> el, tail, rest, tail_position}
end
end)
encoded = encode_head_and_data(Tuple.to_list(data), types)
{encoded, rest}
end

{head <> tail, rest}
defp encode_type({:array, _type, 0}, [data | rest]) do
encode_type({:tuple, []}, [data |> List.to_tuple | rest])
end

defp encode_type({:array, type, element_count}, [data | rest]) do
repeated_type = Enum.map(1..element_count, fn _ -> type end)

encode_type({:tuple, repeated_type}, [data |> List.to_tuple | rest])
end

Expand All @@ -222,7 +259,7 @@ defmodule ABI.TypeEncoder do
# Note, we'll accept a binary or an integer here, so long as the
# binary is not longer than our allowed data size
defp encode_uint(data, size_in_bits) when rem(size_in_bits, 8) == 0 do
size_in_bytes = ( size_in_bits / 8 ) |> round
size_in_bytes = (size_in_bits / 8) |> round
bin = maybe_encode_unsigned(data)

if byte_size(bin) > size_in_bytes, do: raise "Data overflow encoding uint, data `#{data}` cannot fit in #{size_in_bytes * 8} bits"
Expand All @@ -232,8 +269,8 @@ defmodule ABI.TypeEncoder do

defp pad(bin, size_in_bytes, direction) do
# TODO: Create `left_pad` repo, err, add to `ExthCrypto.Math`
total_size = size_in_bytes + ExthCrypto.Math.mod(32 - size_in_bytes, 32)
padding_size_bits = ( total_size - byte_size(bin) ) * 8
total_size = size_in_bytes + ExthCrypto.Math.mod(ExthCrypto.Math.mod(32 - size_in_bytes, 32), 32)
padding_size_bits = (total_size - byte_size(bin)) * 8
padding = <<0::size(padding_size_bits)>>

case direction do
Expand Down