Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support ints in TypeEncoder #49

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions lib/abi/function_selector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule ABI.FunctionSelector do
| :bytes
| :string
| :address
| {:int, integer()}
| {:array, type}
| {:array, type, non_neg_integer}
| {:tuple, [argument_type]}
Expand Down
44 changes: 41 additions & 3 deletions lib/abi/type_encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ defmodule ABI.TypeEncoder do
...> )
...> |> Base.encode16(case: :lower)
"19c9d90a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000010600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007"

iex> [-255]
...> |> ABI.TypeEncoder.encode(%ABI.FunctionSelector{function: nil, types: [%{type: {:int, 16}}]})
...> |> Base.encode16(case: :lower)
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01"
"""
def encode(data, function_selector) do
encode_method_id(function_selector) <> do_encode_data(data, function_selector)
Expand Down Expand Up @@ -255,6 +260,10 @@ defmodule ABI.TypeEncoder do
{encode_uint(data, size), rest}
end

defp encode_type({:int, size}, [data | rest]) do
{encode_int(data, size), rest}
end

defp encode_type(:address, data), do: encode_type({:uint, 160}, data)

defp encode_type(:bool, [data | rest]) do
Expand Down Expand Up @@ -342,6 +351,29 @@ defmodule ABI.TypeEncoder do
bytes |> pad(byte_size(bytes), :right)
end

defp encode_int(int, desired_size_bits) when rem(desired_size_bits, 8) == 0 and is_integer(int) do
desired_size_bytes = ceil(desired_size_bits / 8)

sign_byte = if(int < 0, do: <<0xFF>>, else: <<0x00>>)

significant_bytes =
if int >= 0 do
maybe_encode_unsigned(abs(int))
else
# two's complement encoding: 2**(integer_bit_size) - abs(integer)
actual_bit_size = :binary.encode_unsigned(-1 * int) |> bit_size()
maybe_encode_unsigned(2 ** actual_bit_size + int)
end

if byte_size(significant_bytes) > desired_size_bytes - 1 do
raise(
"Data overflow encoding int, data `#{int}` cannot fit in #{(desired_size_bytes - 1) * 8} bits"
)
end

pad(significant_bytes, desired_size_bytes, :left, fill_byte: sign_byte)
end

# 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
Expand All @@ -357,12 +389,18 @@ defmodule ABI.TypeEncoder do
bin |> pad(size_in_bytes, :left)
end

defp pad(bin, size_in_bytes, direction) do
defp pad(bin, size_in_bytes, direction, opts \\ []) do
fill_byte = Keyword.get(opts, :fill_byte, <<0x00>>)

# TODO: Create `left_pad` repo, err, add to `ABI.Math`
total_size = size_in_bytes + ABI.Math.mod(32 - ABI.Math.mod(size_in_bytes, 32), 32)

padding_size_bits = (total_size - byte_size(bin)) * 8
padding = <<0::size(padding_size_bits)>>
padding_size_bytes = total_size - byte_size(bin)

padding =
Stream.duplicate(fill_byte, padding_size_bytes)
|> Enum.to_list()
|> :binary.list_to_bin()

case direction do
:left -> padding <> bin
Expand Down