diff --git a/lib/ethers/transaction.ex b/lib/ethers/transaction.ex index 030cda5..bb81cfe 100644 --- a/lib/ethers/transaction.ex +++ b/lib/ethers/transaction.ex @@ -109,6 +109,50 @@ defmodule Ethers.Transaction do |> prepend_type_envelope(type) end + # Assuming the input is always a single string, the signed tx + # We could just leave the (most likely present) 0x on and just pass it around, + # i don't think hex_decode cares, but it makes inspecting the possible prexfix easier to remove it - discuss + def decode("0x" <> signed_tx), do: decode(signed_tx) + def decode(signed_tx), do: decode(decode_tx_and_type(signed_tx)) + def decode(:eip1559, decoded) do + # TODO we could inspect the length and throw if not 12.. + # [chain_id, nonce, max_priority_fee, max_fee, gas_limit, to, value, data, access_list, y, r, s] + vector = ExRLP.decode(decoded) + + # no recover the public key (from), we first need to reconstruct the unsigned 'digest' + # NOTE: after the digest is assembled we also prepend the type envelope + tx_hash = + Enum.take(vector, 9) + |> ExRLP.encode() + |> prepend_type_envelope(:eip1559) + |> ExKeccak.hash_256() + + [y, r, s] = Enum.take(vector, -3) + + {:ok, public_key} = ExSecp256k1.recover(tx_hash, r, s, :binary.decode_unsigned(y)) + + {:ok, struct!(__MODULE__, %{ + access_list: Enum.at(vector, 8), + chain_id: Enum.at(vector, 0), + data: Enum.at(vector, 7), + from: Utils.public_key_to_address(public_key), + gas: Enum.at(vector, 4), + max_fee_per_gas: Enum.at(vector, 3), + max_priority_fee_per_gas: Enum.at(vector, 2), + nonce: Enum.at(vector, 1), + signature_r: r, + signature_s: s, + signature_y_parity_or_v: y, + to: Enum.at(5), + type: :eip1559, + value: Enum.at(6) + })} + end + + def decode(:legacy, decoded), do: decoded + + def decode(_type, _decoded), do: {:error, :unsupported_tx_type} + def from_map(tx) do with {:ok, tx_type} <- decode_tx_type(from_map_value(tx, :type)) do tx_struct = @@ -306,6 +350,18 @@ defmodule Ethers.Transaction do end end + # another possibility is more overrides for the local hex_decode, but... slicing the prefix seems ok? discuss + defp decode_tx_and_type(full_sig) do + {prefix, sig} = String.split_at(full_sig, 2) + + case prefix do + "03" -> {:eip4844, Utils.hex_decode(sig)} + "02" -> {:eip1559, Utils.hex_decode(sig)} + "01" -> {:eip2930, Utils.hex_decode(sig)} + _ -> {:legacy, Utils.hex_decode(full_sig)} + end + end + defp from_map_value(tx, key) do Map.get_lazy(tx, key, fn -> Map.get(tx, to_string(key)) end) end diff --git a/test/ethers/transaction_test.exs b/test/ethers/transaction_test.exs index a6f61fd..c8292b4 100644 --- a/test/ethers/transaction_test.exs +++ b/test/ethers/transaction_test.exs @@ -101,4 +101,12 @@ defmodule Ethers.TransactionTest do Transaction.encode(transaction) |> Ethers.Utils.hex_encode() end end + + describe "decode/1" do + test "decodes type2" do + sig = "0x02f8a70103020482a41094826180541412d574cf1336d22c0c0a287822678a80b844a9059cbb000000000000000000000000b1228c21546e3848498169f4241ab63279d502d5000000000000000000000000000000000000000000000000000000e8d4a51000c080a0b37b7057eaf42412f834b4e3f1cd00ed4a346381ac2bdd7ddc2fad48619453b3a00868d3423b2e365793bc691be1f7611e3d09c8e883535a1d7f25c2d2f8ee514c" + assert {:ok, tx} = Transaction.decode(sig) + dbg(tx) + end + end end