From 8f5b7cce59a82a6e9423398e3e55fdefb1184e7e Mon Sep 17 00:00:00 2001 From: fluffywaffles Date: Thu, 16 Jan 2025 14:57:09 -0800 Subject: [PATCH 1/2] feat: support ints in TypeEncoder --- lib/abi/function_selector.ex | 1 + lib/abi/type_encoder.ex | 44 +++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/abi/function_selector.ex b/lib/abi/function_selector.ex index 67695b0..0a918be 100644 --- a/lib/abi/function_selector.ex +++ b/lib/abi/function_selector.ex @@ -12,6 +12,7 @@ defmodule ABI.FunctionSelector do | :bytes | :string | :address + | {:int, integer()} | {:array, type} | {:array, type, non_neg_integer} | {:tuple, [argument_type]} diff --git a/lib/abi/type_encoder.ex b/lib/abi/type_encoder.ex index 612a8ca..b52ae4a 100644 --- a/lib/abi/type_encoder.ex +++ b/lib/abi/type_encoder.ex @@ -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) @@ -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 @@ -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 @@ -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 From 4def8bbd9f05b5977135887032d5769c36a915f5 Mon Sep 17 00:00:00 2001 From: fluffywaffles Date: Thu, 16 Jan 2025 16:11:51 -0800 Subject: [PATCH 2/2] chore: increment minor version to include integer encoder --- README.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac56336..624ebc9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ by adding `abi` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:abi, "~> 1.1.0"} + {:abi, "~> 1.2.0"} ] end ``` diff --git a/mix.exs b/mix.exs index 5883eb6..daf7be8 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ABI.Mixfile do def project do [ app: :abi, - version: "1.1.0", + version: "1.2.0", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), description: "Ethereum's ABI Interface",