From 775edd97013e009072e96a7cd95397fd7ec44e16 Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Mon, 8 Jan 2024 17:29:07 -0500 Subject: [PATCH 01/13] decode tx body --- lib/ethers.ex | 6 +++- lib/ethers/transaction.ex | 68 +++++++++++++++++++++++++++++++++++++-- test/ethers_test.exs | 6 ++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/lib/ethers.ex b/lib/ethers.ex index 3542abe..974246a 100644 --- a/lib/ethers.ex +++ b/lib/ethers.ex @@ -146,7 +146,7 @@ defmodule Ethers do - rpc_opts: Specific RPC options to specify for this request. """ @spec get_transaction(Types.t_hash(), Keyword.t()) :: - {:ok, map()} | {:error, term()} + {:ok, Transaction.t()} | {:error, term()} def get_transaction(tx_hash, opts \\ []) when is_binary(tx_hash) do {rpc_client, rpc_opts} = get_rpc_client(opts) @@ -664,6 +664,10 @@ defmodule Ethers do defp post_process({:ok, nil}, _tx_hash, :get_transaction), do: {:error, :transaction_not_found} + defp post_process({:ok, tx_data}, _tx_hash, :get_transaction) do + Transaction.decode(tx_data) + end + defp post_process({:ok, nil}, _tx_hash, :get_transaction_receipt), do: {:error, :transaction_receipt_not_found} diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 2cb6dd9..ad2c8db 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -22,7 +22,14 @@ defmodule Ethers.Transaction do access_list: [], signature_r: nil, signature_s: nil, - signature_recovery_id: nil + signature_recovery_id: nil, + block_hash: nil, + block_number: nil, + hash: nil, + input: nil, + transaction_index: nil, + v: nil, + y_parity: nil ] @type t_transaction_type :: :legacy | :eip1559 @@ -41,7 +48,14 @@ defmodule Ethers.Transaction do access_list: [{binary(), [binary()]}], signature_r: binary() | nil, signature_s: binary() | nil, - signature_recovery_id: 0 | 1 | nil + signature_recovery_id: 0 | 1 | nil, + block_hash: binary() | nil, + block_number: binary() | nil, + hash: binary() | nil, + input: binary() | nil, + transaction_index: binary() | nil, + v: binary() | nil, + y_parity: binary() | nil } @common_fillable_params [:chain_id, :nonce] @@ -58,7 +72,7 @@ defmodule Ethers.Transaction do {keys, actions} = tx |> Map.from_struct() - |> Map.take(@common_fillable_params ++ Map.fetch!(@type_fillable_params, type)) + |> Map.take(@common_fillable_params ++ Map.get(@type_fillable_params, type)) |> Enum.filter(fn {_k, v} -> is_nil(v) end) |> Enum.map(&elem(&1, 0)) |> Enum.map(&{&1, fill_action(&1, tx)}) @@ -109,6 +123,54 @@ defmodule Ethers.Transaction do |> then(&(<<2>> <> &1)) end + def decode(%{"type" => encoded_type} = tx) do + type = + case Ethers.Utils.hex_to_integer!(encoded_type) do + 2 -> :eip1559 + _ -> :legacy + end + + tx_body = + %{ + chain_id: Map.get(tx, "chainId"), + nonce: Map.get(tx, "nonce"), + gas: Map.get(tx, "gas"), + gas_price: Map.get(tx, "gasPrice"), + max_fee_per_gas: Map.get(tx, "maxFeePerGas"), + max_priority_fee_per_gas: Map.get(tx, "maxPriorityFeePerGas"), + block_number: Map.get(tx, "blockNumber"), + transaction_index: Map.get(tx, "transactionIndex"), + v: Map.get(tx, "v"), + y_parity: Map.get(tx, "yParity") + } + |> Enum.map(fn {k, v} -> + decoded_value = + case v do + nil -> nil + _ -> Ethers.Utils.hex_to_integer!(v) + end + + {k, decoded_value} + end) + |> Enum.into(%{}) + |> Map.merge(%{ + type: type, + from: Map.get(tx, "from"), + to: Map.get(tx, "to"), + data: nil, + access_list: Map.get(tx, "accessList"), + signature_r: Map.get(tx, "r"), + signature_s: Map.get(tx, "s"), + signature_recovery_id: nil, + block_hash: Map.get(tx, "blockHash"), + hash: Map.get(tx, "hash"), + input: Map.get(tx, "input") + }) + |> new() + + {:ok, tx_body} + end + def to_map(%{type: :eip1559} = tx) do %{ from: tx.from, diff --git a/test/ethers_test.exs b/test/ethers_test.exs index a4a1300..578138d 100644 --- a/test/ethers_test.exs +++ b/test/ethers_test.exs @@ -78,9 +78,9 @@ defmodule EthersTest do assert {:ok, %{ - "hash" => ^tx_hash, - "from" => @from, - "to" => ^downcased_to_addr + hash: ^tx_hash, + from: @from, + to: ^downcased_to_addr }} = Ethers.get_transaction(tx_hash) end From 62a847ff13d18882f034c1236e7c361e96ea745f Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Mon, 8 Jan 2024 17:35:57 -0500 Subject: [PATCH 02/13] revert wrong commit --- lib/ethers/transaction.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index ad2c8db..708ea22 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -72,7 +72,7 @@ defmodule Ethers.Transaction do {keys, actions} = tx |> Map.from_struct() - |> Map.take(@common_fillable_params ++ Map.get(@type_fillable_params, type)) + |> Map.take(@common_fillable_params ++ Map.fetch!(@type_fillable_params, type)) |> Enum.filter(fn {_k, v} -> is_nil(v) end) |> Enum.map(&elem(&1, 0)) |> Enum.map(&{&1, fill_action(&1, tx)}) From dd9128c46ccbcf677c0465a31c2155468ca6e2f9 Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Fri, 12 Jan 2024 12:46:47 -0500 Subject: [PATCH 03/13] cleanup --- lib/ethers/transaction.ex | 56 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 708ea22..6507847 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -123,38 +123,27 @@ defmodule Ethers.Transaction do |> then(&(<<2>> <> &1)) end - def decode(%{"type" => encoded_type} = tx) do - type = - case Ethers.Utils.hex_to_integer!(encoded_type) do + def decode(tx) when is_map(tx) do + tx_type = + case decode_key(tx, "type") do 2 -> :eip1559 - _ -> :legacy + 1 -> :legacy + _ -> nil end tx_body = %{ - chain_id: Map.get(tx, "chainId"), - nonce: Map.get(tx, "nonce"), - gas: Map.get(tx, "gas"), - gas_price: Map.get(tx, "gasPrice"), - max_fee_per_gas: Map.get(tx, "maxFeePerGas"), - max_priority_fee_per_gas: Map.get(tx, "maxPriorityFeePerGas"), - block_number: Map.get(tx, "blockNumber"), - transaction_index: Map.get(tx, "transactionIndex"), - v: Map.get(tx, "v"), - y_parity: Map.get(tx, "yParity") - } - |> Enum.map(fn {k, v} -> - decoded_value = - case v do - nil -> nil - _ -> Ethers.Utils.hex_to_integer!(v) - end - - {k, decoded_value} - end) - |> Enum.into(%{}) - |> Map.merge(%{ - type: type, + type: tx_type, + chain_id: decode_key(tx, "chainId"), + nonce: decode_key(tx, "nonce"), + gas: decode_key(tx, "gas"), + gas_price: decode_key(tx, "gasPrice"), + max_fee_per_gas: decode_key(tx, "maxFeePerGas"), + max_priority_fee_per_gas: decode_key(tx, "maxPriorityFeePerGas"), + block_number: decode_key(tx, "blockNumber"), + transaction_index: decode_key(tx, "transactionIndex"), + v: decode_key(tx, "v"), + y_parity: decode_key(tx, "yParity"), from: Map.get(tx, "from"), to: Map.get(tx, "to"), data: nil, @@ -165,8 +154,8 @@ defmodule Ethers.Transaction do block_hash: Map.get(tx, "blockHash"), hash: Map.get(tx, "hash"), input: Map.get(tx, "input") - }) - |> new() + } + |> new(tx_type) {:ok, tx_body} end @@ -272,4 +261,13 @@ defmodule Ethers.Transaction do defp trim_leading(<<0, rest::binary>>), do: trim_leading(rest) defp trim_leading(<>), do: bin + + defp decode_key(tx, key) do + decoded_value = Map.get(tx, key) |> Ethers.Utils.hex_to_integer() + + case decoded_value do + {:ok, v} -> v + {:error, :invalid_hex} -> nil + end + end end From e287fac0a73e9d0397a34d328dbc666371830364 Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Mon, 15 Jan 2024 17:31:36 -0500 Subject: [PATCH 04/13] address review comments --- lib/ethers/transaction.ex | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 6507847..95159e3 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -143,6 +143,7 @@ defmodule Ethers.Transaction do block_number: decode_key(tx, "blockNumber"), transaction_index: decode_key(tx, "transactionIndex"), v: decode_key(tx, "v"), + value: decode_key(tx, "value"), y_parity: decode_key(tx, "yParity"), from: Map.get(tx, "from"), to: Map.get(tx, "to"), @@ -263,11 +264,17 @@ defmodule Ethers.Transaction do defp trim_leading(<>), do: bin defp decode_key(tx, key) do - decoded_value = Map.get(tx, key) |> Ethers.Utils.hex_to_integer() + value = Map.get(tx, key) - case decoded_value do - {:ok, v} -> v - {:error, :invalid_hex} -> nil + case value do + nil -> + nil + + _ -> + case Ethers.Utils.hex_to_integer(value) do + {:ok, v} -> v + {:error, :invalid_hex} -> nil + end end end end From de978b0a179378a50eddfe78ee02cb86b73391b6 Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 14:57:53 -0500 Subject: [PATCH 05/13] Change tx value from_map decoding type to binary --- lib/ethers.ex | 2 +- lib/ethers/transaction.ex | 105 ++++++++++++++++++-------------------- test/ethers_test.exs | 2 +- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/lib/ethers.ex b/lib/ethers.ex index 974246a..96ac254 100644 --- a/lib/ethers.ex +++ b/lib/ethers.ex @@ -665,7 +665,7 @@ defmodule Ethers do do: {:error, :transaction_not_found} defp post_process({:ok, tx_data}, _tx_hash, :get_transaction) do - Transaction.decode(tx_data) + Transaction.from_map(tx_data) end defp post_process({:ok, nil}, _tx_hash, :get_transaction_receipt), diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 95159e3..31e92f0 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -23,16 +23,14 @@ defmodule Ethers.Transaction do signature_r: nil, signature_s: nil, signature_recovery_id: nil, + signature_y_parity: nil, block_hash: nil, block_number: nil, hash: nil, - input: nil, - transaction_index: nil, - v: nil, - y_parity: nil + transaction_index: nil ] - @type t_transaction_type :: :legacy | :eip1559 + @type t_transaction_type :: :legacy | :eip1559 | :eip2930 | :eip4844 @type t :: %__MODULE__{ type: t_transaction_type(), chain_id: binary() | nil, @@ -52,10 +50,8 @@ defmodule Ethers.Transaction do block_hash: binary() | nil, block_number: binary() | nil, hash: binary() | nil, - input: binary() | nil, transaction_index: binary() | nil, - v: binary() | nil, - y_parity: binary() | nil + signature_y_parity: binary() | nil } @common_fillable_params [:chain_id, :nonce] @@ -123,42 +119,38 @@ defmodule Ethers.Transaction do |> then(&(<<2>> <> &1)) end - def decode(tx) when is_map(tx) do - tx_type = - case decode_key(tx, "type") do - 2 -> :eip1559 - 1 -> :legacy - _ -> nil - end + def encode(%{type: type}) do + raise "Ethers does not support encoding of #{inspect(type)} transactions" + end + + def from_map(tx) do + with {:ok, tx_type} <- decode_tx_type(from_map_value(tx, :type)) do + tx_struct = + %{ + access_list: from_map_value(tx, :accessList), + block_hash: from_map_value(tx, :blockHash), + block_number: from_map_value(tx, :blockNumber), + chain_id: from_map_value(tx, :chainId), + data: from_map_value(tx, :input), + from: from_map_value(tx, :from), + gas: from_map_value(tx, :gas), + gas_price: from_map_value(tx, :gasPrice), + hash: from_map_value(tx, :hash), + max_fee_per_gas: from_map_value(tx, :maxFeePerGas), + max_priority_fee_per_gas: from_map_value(tx, :maxPriorityFeePerGas), + nonce: from_map_value(tx, :nonce), + signature_r: from_map_value(tx, :r), + signature_recovery_id: from_map_value(tx, :v), + signature_s: from_map_value(tx, :s), + signature_y_parity: from_map_value(tx, :yParity), + to: from_map_value(tx, :to), + transaction_index: from_map_value(tx, :transactionIndex), + value: from_map_value(tx, :value) + } + |> new(tx_type) - tx_body = - %{ - type: tx_type, - chain_id: decode_key(tx, "chainId"), - nonce: decode_key(tx, "nonce"), - gas: decode_key(tx, "gas"), - gas_price: decode_key(tx, "gasPrice"), - max_fee_per_gas: decode_key(tx, "maxFeePerGas"), - max_priority_fee_per_gas: decode_key(tx, "maxPriorityFeePerGas"), - block_number: decode_key(tx, "blockNumber"), - transaction_index: decode_key(tx, "transactionIndex"), - v: decode_key(tx, "v"), - value: decode_key(tx, "value"), - y_parity: decode_key(tx, "yParity"), - from: Map.get(tx, "from"), - to: Map.get(tx, "to"), - data: nil, - access_list: Map.get(tx, "accessList"), - signature_r: Map.get(tx, "r"), - signature_s: Map.get(tx, "s"), - signature_recovery_id: nil, - block_hash: Map.get(tx, "blockHash"), - hash: Map.get(tx, "hash"), - input: Map.get(tx, "input") - } - |> new(tx_type) - - {:ok, tx_body} + {:ok, tx_struct} + end end def to_map(%{type: :eip1559} = tx) do @@ -188,6 +180,9 @@ defmodule Ethers.Transaction do defp maybe_add_signature(tx_list, tx) do case tx do + %{signature_r: r, signature_s: s, signature_y_parity: y_parity} when not is_nil(y_parity) -> + tx_list ++ [y_parity, trim_leading(r), trim_leading(s)] + %{signature_r: r, signature_s: s, signature_recovery_id: rec_id} when not is_nil(r) -> y_parity = case tx do @@ -263,18 +258,18 @@ defmodule Ethers.Transaction do defp trim_leading(<<0, rest::binary>>), do: trim_leading(rest) defp trim_leading(<>), do: bin - defp decode_key(tx, key) do - value = Map.get(tx, key) - - case value do - nil -> - nil - - _ -> - case Ethers.Utils.hex_to_integer(value) do - {:ok, v} -> v - {:error, :invalid_hex} -> nil - end + defp decode_tx_type(type) do + case type do + "0x3" -> {:ok, :eip4844} + "0x2" -> {:ok, :eip1559} + "0x1" -> {:ok, :eip2930} + "0x0" -> {:ok, :legacy} + nil -> {:ok, :legacy} + _ -> {:error, :unsupported_tx_type} end end + + defp from_map_value(tx, key) do + Map.get_lazy(tx, key, fn -> Map.get(tx, to_string(key)) end) + end end diff --git a/test/ethers_test.exs b/test/ethers_test.exs index 578138d..9c128ff 100644 --- a/test/ethers_test.exs +++ b/test/ethers_test.exs @@ -77,7 +77,7 @@ defmodule EthersTest do downcased_to_addr = String.downcase(@to) assert {:ok, - %{ + %Ethers.Transaction{ hash: ^tx_hash, from: @from, to: ^downcased_to_addr From dfb152727defc4603022b12c093851ebf2366b0a Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 15:43:43 -0500 Subject: [PATCH 06/13] Add Transaction.decode_values/1 function --- lib/ethers/transaction.ex | 45 ++++++++++++++++++++---- test/ethers/transaction_test.exs | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 test/ethers/transaction_test.exs diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 31e92f0..f1c897e 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -51,7 +51,7 @@ defmodule Ethers.Transaction do block_number: binary() | nil, hash: binary() | nil, transaction_index: binary() | nil, - signature_y_parity: binary() | nil + signature_y_parity: non_neg_integer() | nil } @common_fillable_params [:chain_id, :nonce] @@ -59,6 +59,22 @@ defmodule Ethers.Transaction do legacy: [:gas_price], eip1559: [:max_fee_per_gas] } + @integer_type_values [ + :block_number, + :chain_id, + :gas, + :gas_price, + :max_fee_per_gas, + :max_priority_fee_per_gas, + :nonce, + :signature_recovery_id, + :signature_y_parity, + :transaction_index, + :value + ] + @binary_type_values [:data, :signature_r, :signature_s] + + defguardp has_value(v) when not is_nil(v) and v != "" def new(params, type \\ :eip1559) do struct!(__MODULE__, Map.put(params, :type, type)) @@ -94,7 +110,6 @@ defmodule Ethers.Transaction do tx.value, tx.data ] - |> Enum.map(&(&1 || "")) |> maybe_add_signature(tx) |> convert_to_binary() |> ExRLP.encode() @@ -112,7 +127,6 @@ defmodule Ethers.Transaction do tx.data, tx.access_list ] - |> Enum.map(&(&1 || "")) |> maybe_add_signature(tx) |> convert_to_binary() |> ExRLP.encode() @@ -178,17 +192,31 @@ defmodule Ethers.Transaction do } end + @doc """ + Decodes a transaction struct values in a new map. + """ + @spec decode_values(t()) :: Map.t() + def decode_values(%__MODULE__{} = tx) do + tx + |> Map.from_struct() + |> Map.new(fn + {k, v} when k in @integer_type_values -> {k, Utils.hex_to_integer!(v)} + {k, v} when k in @binary_type_values -> {k, Utils.hex_decode!(v)} + {k, v} -> {k, v} + end) + end + defp maybe_add_signature(tx_list, tx) do case tx do - %{signature_r: r, signature_s: s, signature_y_parity: y_parity} when not is_nil(y_parity) -> + %{signature_r: r, signature_s: s, signature_y_parity: y_parity} when has_value(y_parity) -> tx_list ++ [y_parity, trim_leading(r), trim_leading(s)] - %{signature_r: r, signature_s: s, signature_recovery_id: rec_id} when not is_nil(r) -> + %{signature_r: r, signature_s: s, signature_recovery_id: rec_id} when has_value(r) -> y_parity = case tx do %{type: :legacy, chain_id: chain_id} when not is_nil(chain_id) -> # EIP-155 - chain_id = Ethers.Utils.hex_to_integer!(chain_id) + chain_id = Utils.hex_to_integer!(chain_id) rec_id + 35 + chain_id * 2 %{type: :legacy} -> @@ -244,12 +272,15 @@ defmodule Ethers.Transaction do Enum.map(list, fn "0x" <> _ = bin -> bin - |> Ethers.Utils.hex_decode!() + |> Utils.hex_decode!() |> trim_leading() l when is_list(l) -> convert_to_binary(l) + nil -> + "" + item -> item end) diff --git a/test/ethers/transaction_test.exs b/test/ethers/transaction_test.exs new file mode 100644 index 0000000..cb827d3 --- /dev/null +++ b/test/ethers/transaction_test.exs @@ -0,0 +1,59 @@ +defmodule Ethers.TransactionTest do + use ExUnit.Case + + alias Ethers.Transaction + + @transaction_fixture %Ethers.Transaction{ + type: :eip1559, + chain_id: "0x539", + nonce: "0x516", + gas: "0x5d30", + from: "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1", + to: "0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc", + value: "0x0", + data: + "0x435ffe940000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001268656c6c6f206c6f63616c207369676e65720000000000000000000000000000", + gas_price: "0x8", + max_fee_per_gas: "0x8f0d1800", + max_priority_fee_per_gas: "0x0", + access_list: [], + signature_r: "0x639e5b615f34498f3e5a03f4831e4b7a2a1d5b61ed1388181ef7689c01466fc3", + signature_s: "0x34a9311fae88125c4f9df5d0ed61f8e37bbaf62681f3ce96d03899114df8997", + signature_recovery_id: "0x1", + signature_y_parity: "0x1", + block_hash: "0xa2b720a9653afd26411e9bc94283cc496cd3d763378a67fd645bf1a4e332f37d", + block_number: "0x595", + hash: "0xdc78c7e7ea3a5980f732e466daf1fdc4f009e973530d7e84f0b2012f1ff2cfc7", + transaction_index: "0x0" + } + + describe "decode_values/1" do + test "decodes the transaction values to correct types" do + decoded = Transaction.decode_values(@transaction_fixture) + + assert %{ + type: :eip1559, + value: 0, + to: "0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc", + hash: "0xdc78c7e7ea3a5980f732e466daf1fdc4f009e973530d7e84f0b2012f1ff2cfc7", + from: "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1", + gas: 23856, + block_number: 1429, + gas_price: 8, + max_fee_per_gas: 2_400_000_000, + chain_id: 1337, + nonce: 1302, + block_hash: "0xa2b720a9653afd26411e9bc94283cc496cd3d763378a67fd645bf1a4e332f37d", + transaction_index: 0, + max_priority_fee_per_gas: 0, + access_list: [], + signature_recovery_id: 1, + signature_y_parity: 1 + } = decoded + + assert is_binary(decoded.data) + assert is_binary(decoded.signature_r) + assert is_binary(decoded.signature_s) + end + end +end From 1a06ca2f3a7523e7dd8fca87054aaed7999ae899 Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 15:50:01 -0500 Subject: [PATCH 07/13] Cleanup signature y_parity calculation --- lib/ethers/transaction.ex | 44 ++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index f1c897e..41a328e 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -208,27 +208,8 @@ defmodule Ethers.Transaction do defp maybe_add_signature(tx_list, tx) do case tx do - %{signature_r: r, signature_s: s, signature_y_parity: y_parity} when has_value(y_parity) -> - tx_list ++ [y_parity, trim_leading(r), trim_leading(s)] - - %{signature_r: r, signature_s: s, signature_recovery_id: rec_id} when has_value(r) -> - y_parity = - case tx do - %{type: :legacy, chain_id: chain_id} when not is_nil(chain_id) -> - # EIP-155 - chain_id = Utils.hex_to_integer!(chain_id) - rec_id + 35 + chain_id * 2 - - %{type: :legacy} -> - # EIP-155 - rec_id + 27 - - _ -> - # EIP-1559 - rec_id - end - - tx_list ++ [y_parity, trim_leading(r), trim_leading(s)] + %{signature_r: r, signature_s: s} when has_value(r) and has_value(s) -> + tx_list ++ [get_y_parity(tx), trim_leading(r), trim_leading(s)] %{type: :legacy, chain_id: chain_id} when not is_nil(chain_id) -> # EIP-155 encoding for signature mitigation intra-chain replay attack @@ -286,6 +267,27 @@ defmodule Ethers.Transaction do end) end + defp get_y_parity(%{signature_y_parity: y_parity}) when has_value(y_parity) do + y_parity + end + + defp get_y_parity(%{signature_recovery_id: rec_id} = tx) when has_value(rec_id) do + case tx do + %{type: :legacy, chain_id: chain_id} when has_value(chain_id) -> + # EIP-155 + chain_id = Utils.hex_to_integer!(chain_id) + rec_id + 35 + chain_id * 2 + + %{type: :legacy} -> + # EIP-155 + rec_id + 27 + + _ -> + # EIP-1559 + rec_id + end + end + defp trim_leading(<<0, rest::binary>>), do: trim_leading(rest) defp trim_leading(<>), do: bin From 217d7805534e5994ac23cdc7e37f2f890a1b31b4 Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 15:53:38 -0500 Subject: [PATCH 08/13] Fix credo issue --- test/ethers/transaction_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ethers/transaction_test.exs b/test/ethers/transaction_test.exs index cb827d3..e2cbfde 100644 --- a/test/ethers/transaction_test.exs +++ b/test/ethers/transaction_test.exs @@ -37,7 +37,7 @@ defmodule Ethers.TransactionTest do to: "0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc", hash: "0xdc78c7e7ea3a5980f732e466daf1fdc4f009e973530d7e84f0b2012f1ff2cfc7", from: "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1", - gas: 23856, + gas: 23_856, block_number: 1429, gas_price: 8, max_fee_per_gas: 2_400_000_000, From d7b44f0aac57e64dc0a30041f2c7e946d1942601 Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 15:57:23 -0500 Subject: [PATCH 09/13] Fix typespec of decode_values function --- lib/ethers/transaction.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 41a328e..c82d798 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -195,7 +195,7 @@ defmodule Ethers.Transaction do @doc """ Decodes a transaction struct values in a new map. """ - @spec decode_values(t()) :: Map.t() + @spec decode_values(t()) :: map() def decode_values(%__MODULE__{} = tx) do tx |> Map.from_struct() From 27dbc15fd7494a1e899ca278da8a36f47cbd5ce7 Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 16:18:02 -0500 Subject: [PATCH 10/13] Support get_transaction in batch requests --- lib/ethers.ex | 1 + test/ethers_test.exs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/ethers.ex b/lib/ethers.ex index 96ac254..c880640 100644 --- a/lib/ethers.ex +++ b/lib/ethers.ex @@ -84,6 +84,7 @@ defmodule Ethers do gas_price: :eth_gas_price, get_logs: :eth_get_logs, get_transaction_count: :eth_get_transaction_count, + get_transaction: :eth_get_transaction_by_hash, send: :eth_send_transaction } diff --git a/test/ethers_test.exs b/test/ethers_test.exs index 9c128ff..4ca8106 100644 --- a/test/ethers_test.exs +++ b/test/ethers_test.exs @@ -84,6 +84,27 @@ defmodule EthersTest do }} = Ethers.get_transaction(tx_hash) end + test "works in batch requests" do + {:ok, tx_hash} = + HelloWorldContract.set_hello("hello local signer") + |> Ethers.send( + from: @from, + to: @to, + signer: Ethers.Signer.Local, + signer_opts: [ + private_key: "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" + ] + ) + + assert {:ok, + [ + ok: %Ethers.Transaction{hash: ^tx_hash} + ]} = + Ethers.batch([ + {:get_transaction, tx_hash} + ]) + end + test "returns error by non-existent tx_hash" do assert {:error, :transaction_not_found} = Ethers.get_transaction( From 392f133f82d018851ed64a1f30d94773836e3bbd Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Sun, 28 Jan 2024 20:23:58 -0500 Subject: [PATCH 11/13] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b829f0..140de82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ - Add `Ethers.get_transaction_receipt/2` function to query native chain transaction receipt by transaction hash. +### Enhancements + +- Add more metadata to `Ethers.Transaction` struct. +- Return `Ethers.Transaction` struct in `Ethers.get_transaction/2` function. +- Support `get_transaction` in batch requests. + ## v0.2.2 (2023-01-08) ### New features From 83d526d83a5c8c7b50e3e5cb97c81a2371e824ba Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Mon, 29 Jan 2024 12:12:32 -0500 Subject: [PATCH 12/13] Fix raise on missing values --- lib/ethers/transaction.ex | 2 ++ test/ethers/transaction_test.exs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index c82d798..04ae4e4 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -200,6 +200,8 @@ defmodule Ethers.Transaction do tx |> Map.from_struct() |> Map.new(fn + {k, nil} -> {k, nil} + {k, ""} -> {k, nil} {k, v} when k in @integer_type_values -> {k, Utils.hex_to_integer!(v)} {k, v} when k in @binary_type_values -> {k, Utils.hex_decode!(v)} {k, v} -> {k, v} diff --git a/test/ethers/transaction_test.exs b/test/ethers/transaction_test.exs index e2cbfde..1384cb9 100644 --- a/test/ethers/transaction_test.exs +++ b/test/ethers/transaction_test.exs @@ -55,5 +55,13 @@ defmodule Ethers.TransactionTest do assert is_binary(decoded.signature_r) assert is_binary(decoded.signature_s) end + + test "does not fail with missing values" do + assert %{signature_recovery_id: nil} = + Transaction.decode_values(%{@transaction_fixture | signature_recovery_id: nil}) + + assert %{signature_recovery_id: nil} = + Transaction.decode_values(%{@transaction_fixture | signature_recovery_id: ""}) + end end end From 3b70c6eff9759859536d4c8e88eb6693e46e6cca Mon Sep 17 00:00:00 2001 From: Alisina Bahadori Date: Mon, 29 Jan 2024 13:29:38 -0500 Subject: [PATCH 13/13] Re-add signature_v --- lib/ethers/transaction.ex | 16 +++++++++++----- test/ethers/transaction_test.exs | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 04ae4e4..36cbc32 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -22,6 +22,7 @@ defmodule Ethers.Transaction do access_list: [], signature_r: nil, signature_s: nil, + signature_v: nil, signature_recovery_id: nil, signature_y_parity: nil, block_hash: nil, @@ -46,12 +47,13 @@ defmodule Ethers.Transaction do access_list: [{binary(), [binary()]}], signature_r: binary() | nil, signature_s: binary() | nil, - signature_recovery_id: 0 | 1 | nil, + signature_v: binary() | non_neg_integer() | nil, + signature_y_parity: binary() | non_neg_integer() | nil, + signature_recovery_id: binary() | 0 | 1 | nil, block_hash: binary() | nil, block_number: binary() | nil, hash: binary() | nil, - transaction_index: binary() | nil, - signature_y_parity: non_neg_integer() | nil + transaction_index: binary() | nil } @common_fillable_params [:chain_id, :nonce] @@ -69,12 +71,13 @@ defmodule Ethers.Transaction do :nonce, :signature_recovery_id, :signature_y_parity, + :signature_v, :transaction_index, :value ] @binary_type_values [:data, :signature_r, :signature_s] - defguardp has_value(v) when not is_nil(v) and v != "" + defguardp has_value(v) when not is_nil(v) and v != "" and v != "0x" def new(params, type \\ :eip1559) do struct!(__MODULE__, Map.put(params, :type, type)) @@ -154,8 +157,9 @@ defmodule Ethers.Transaction do max_priority_fee_per_gas: from_map_value(tx, :maxPriorityFeePerGas), nonce: from_map_value(tx, :nonce), signature_r: from_map_value(tx, :r), - signature_recovery_id: from_map_value(tx, :v), signature_s: from_map_value(tx, :s), + signature_v: from_map_value(tx, :v), + signature_recovery_id: from_map_value(tx, :v), signature_y_parity: from_map_value(tx, :yParity), to: from_map_value(tx, :to), transaction_index: from_map_value(tx, :transactionIndex), @@ -290,6 +294,8 @@ defmodule Ethers.Transaction do end end + defp get_y_parity(%{type: :legacy, signature_v: v}) when has_value(v), do: v + defp trim_leading(<<0, rest::binary>>), do: trim_leading(rest) defp trim_leading(<>), do: bin diff --git a/test/ethers/transaction_test.exs b/test/ethers/transaction_test.exs index 1384cb9..74fe986 100644 --- a/test/ethers/transaction_test.exs +++ b/test/ethers/transaction_test.exs @@ -21,6 +21,7 @@ defmodule Ethers.TransactionTest do signature_s: "0x34a9311fae88125c4f9df5d0ed61f8e37bbaf62681f3ce96d03899114df8997", signature_recovery_id: "0x1", signature_y_parity: "0x1", + signature_v: "0x1", block_hash: "0xa2b720a9653afd26411e9bc94283cc496cd3d763378a67fd645bf1a4e332f37d", block_number: "0x595", hash: "0xdc78c7e7ea3a5980f732e466daf1fdc4f009e973530d7e84f0b2012f1ff2cfc7", @@ -48,7 +49,8 @@ defmodule Ethers.TransactionTest do max_priority_fee_per_gas: 0, access_list: [], signature_recovery_id: 1, - signature_y_parity: 1 + signature_y_parity: 1, + signature_v: 1 } = decoded assert is_binary(decoded.data)