From 76d791d350a284483cee5ff9f1e6883e087f9320 Mon Sep 17 00:00:00 2001 From: James Duncombe Date: Tue, 30 Jan 2024 12:22:21 +0000 Subject: [PATCH] wip --- config/config.exs | 1 + lib/tt_eth.ex | 9 +- lib/tt_eth/behaviours/chain_client.ex | 6 +- lib/tt_eth/behaviours/transaction.ex | 4 +- lib/tt_eth/behaviours/wallet.ex | 8 ++ lib/tt_eth/chain_client.ex | 5 +- lib/tt_eth/chain_client_mock_impl.ex | 5 +- lib/tt_eth/local_wallet.ex | 66 +++++++++++++++ lib/tt_eth/protocols/wallet.ex | 16 ++++ .../transactions/eip1559_transaction.ex | 28 +++---- lib/tt_eth/transactions/legacy_transaction.ex | 51 +++++++----- lib/tt_eth/type/signature.ex | 3 +- lib/tt_eth/wallet.ex | 83 ++++++++++++++----- mix.exs | 8 ++ test/tt_eth/local_wallet_test.exs | 5 ++ .../transactions/eip1559_transaction_test.exs | 18 ++-- test/tt_eth/wallet_test.exs | 24 +++--- test/tt_eth_test.exs | 8 +- 18 files changed, 248 insertions(+), 100 deletions(-) create mode 100644 lib/tt_eth/behaviours/wallet.ex create mode 100644 lib/tt_eth/local_wallet.ex create mode 100644 lib/tt_eth/protocols/wallet.ex create mode 100644 test/tt_eth/local_wallet_test.exs diff --git a/config/config.exs b/config/config.exs index a7c9a72..9ae2c52 100644 --- a/config/config.exs +++ b/config/config.exs @@ -10,4 +10,5 @@ config :tt_eth, secondary: "0xe2187dc017e880dded10c403f7c0d397afb11736ac027c1202e318b0dd345086", ternary: "0xfa015243f2e6d8694ab037a7987dc73b1630fc8cb1ce82860344684c15d24026" ], + wallet_adapter: TTEth.LocalWallet, transaction_module: TTEth.Transactions.LegacyTransaction diff --git a/lib/tt_eth.ex b/lib/tt_eth.ex index f3eece7..4118be6 100644 --- a/lib/tt_eth.ex +++ b/lib/tt_eth.ex @@ -69,16 +69,15 @@ defmodule TTEth do @doc "Builds the tx data using the configured `TTEth.ChainClient`." def build_tx_data(%Wallet{} = wallet, to, method, args, opts \\ []) do - with {_, %{private_key: private_key, human_address: human_address}} <- - {:wallet, wallet}, - {_, {:ok, "0x" <> raw_nonce}} <- - {:raw_nonce, human_address |> chain_client().eth_get_transaction_count("pending")}, + with {_, {:ok, "0x" <> raw_nonce}} <- + {:raw_nonce, + wallet.human_address |> chain_client().eth_get_transaction_count("pending")}, {_, {nonce, ""}} <- {:parse_nonce, raw_nonce |> Integer.parse(16)}, {_, abi_data} <- {:abi_encode, method |> ABI.encode(args)} do to - |> chain_client().build_tx_data(abi_data, private_key, nonce, opts) + |> chain_client().build_tx_data(abi_data, wallet, nonce, opts) end end diff --git a/lib/tt_eth/behaviours/chain_client.ex b/lib/tt_eth/behaviours/chain_client.ex index d850cb3..939f452 100644 --- a/lib/tt_eth/behaviours/chain_client.ex +++ b/lib/tt_eth/behaviours/chain_client.ex @@ -6,7 +6,7 @@ defmodule TTEth.Behaviours.ChainClient do """ @type address :: TTEth.Type.Address.t() - @type private_key :: TTEth.Type.PrivateKey.t() + @type wallet :: TTEth.Wallet.t() @type encoded_args :: binary @type opts :: keyword @type nonce :: non_neg_integer() @@ -23,8 +23,8 @@ defmodule TTEth.Behaviours.ChainClient do @callback eth_send_raw_transaction(tx_data) :: any @callback eth_send_raw_transaction(tx_data, opts) :: any - @callback build_tx_data(address, abi_data, private_key, nonce) :: tx_data - @callback build_tx_data(address, abi_data, private_key, nonce, keyword) :: tx_data + @callback build_tx_data(address, abi_data, wallet, nonce) :: tx_data + @callback build_tx_data(address, abi_data, wallet, nonce, keyword) :: tx_data @callback eth_get_transaction_count(account :: address, block_id) :: any @callback eth_get_transaction_count(account :: address, block_id, opts) :: any diff --git a/lib/tt_eth/behaviours/transaction.ex b/lib/tt_eth/behaviours/transaction.ex index f297def..810f1e6 100644 --- a/lib/tt_eth/behaviours/transaction.ex +++ b/lib/tt_eth/behaviours/transaction.ex @@ -9,9 +9,9 @@ defmodule TTEth.Behaviours.Transaction do @type opts :: Keyword.t() @type transaction :: struct() - @type private_key :: binary() + @type wallet :: TTEth.Wallet.t() @callback new(to_address, abi_data, nonce, opts) :: transaction - @callback build(transaction, private_key) :: binary() + @callback build(transaction, wallet) :: binary() end diff --git a/lib/tt_eth/behaviours/wallet.ex b/lib/tt_eth/behaviours/wallet.ex new file mode 100644 index 0000000..29cb5e2 --- /dev/null +++ b/lib/tt_eth/behaviours/wallet.ex @@ -0,0 +1,8 @@ +defmodule TTEth.Behaviours.Wallet do + @moduledoc """ + Behaviour for wallet adapters. + """ + @type config :: map() | binary() + + @callback new(config) :: struct() +end diff --git a/lib/tt_eth/chain_client.ex b/lib/tt_eth/chain_client.ex index 2215863..78728cb 100644 --- a/lib/tt_eth/chain_client.ex +++ b/lib/tt_eth/chain_client.ex @@ -5,6 +5,7 @@ defmodule TTEth.ChainClient do This is agnostic to the transaction version/type. """ alias TTEth.Behaviours.ChainClient + alias TTEth.Wallet alias Ethereumex.HttpClient import TTEth, only: [transaction_module: 0, hex_prefix!: 1] @@ -24,12 +25,12 @@ defmodule TTEth.ChainClient do # Delegate to the transaction module to serialize and sign the transaction. @impl ChainClient - def build_tx_data("" <> to_address, abi_data, private_key, nonce, opts \\ []) + def build_tx_data("" <> to_address, abi_data, %Wallet{} = wallet, nonce, opts \\ []) when is_integer(nonce), do: to_address |> transaction_module().new(abi_data, nonce, opts) - |> transaction_module().build(private_key) + |> transaction_module().build(wallet) |> Base.encode16(case: :lower) |> hex_prefix!() diff --git a/lib/tt_eth/chain_client_mock_impl.ex b/lib/tt_eth/chain_client_mock_impl.ex index 9b01fd8..25d1fe7 100644 --- a/lib/tt_eth/chain_client_mock_impl.ex +++ b/lib/tt_eth/chain_client_mock_impl.ex @@ -3,6 +3,7 @@ defmodule TTEth.ChainClientMockImpl do Implementation default for tests. """ alias TTEth.Behaviours.ChainClient + alias TTEth.Wallet @behaviour ChainClient @@ -15,8 +16,8 @@ defmodule TTEth.ChainClientMockImpl do do: :error @impl ChainClient - def build_tx_data(to, abi_data, private_key, nonce, opts \\ []), - do: to |> TTEth.ChainClient.build_tx_data(abi_data, private_key, nonce, opts) + def build_tx_data(to, abi_data, %Wallet{} = wallet, nonce, opts \\ []), + do: to |> TTEth.ChainClient.build_tx_data(abi_data, wallet, nonce, opts) @impl ChainClient def eth_get_transaction_count(_address, _block \\ "latest", _opts \\ []), diff --git a/lib/tt_eth/local_wallet.ex b/lib/tt_eth/local_wallet.ex new file mode 100644 index 0000000..ee220f3 --- /dev/null +++ b/lib/tt_eth/local_wallet.ex @@ -0,0 +1,66 @@ +defmodule TTEth.LocalWallet do + @moduledoc """ + A local wallet, derived from a private key. + """ + alias TTEth.Type.{Address, PublicKey, PrivateKey} + alias TTEth.Secp256k1 + alias TTEth.Behaviours.Wallet, as: WalletBehaviour + + @type t :: %__MODULE__{} + + @behaviour WalletBehaviour + + defstruct [ + :private_key, + :human_private_key + ] + + defimpl TTEth.Protocols.Wallet, for: __MODULE__ do + def new_attrs(%@for{} = wallet) do + pub = + wallet.private_key + |> PublicKey.from_private_key!() + |> PublicKey.from_human!() + + address = pub |> Address.from_public_key!() + + [ + adapter: @for, + address: address, + public_key: pub, + human_address: address |> Address.to_human!(), + human_public_key: pub |> PublicKey.to_human!(), + _adapter: wallet + ] + end + + def sign(%@for{} = wallet, "" <> digest), + do: + digest + |> Secp256k1.ecdsa_sign_compact(wallet.private_key) + end + + @impl WalletBehaviour + def new(%{private_key: private_key} = _config), + do: + __MODULE__ + |> struct!(%{ + private_key: private_key |> PrivateKey.from_human!(), + human_private_key: private_key + }) + + def new("" <> private_key = _config), + do: new(%{private_key: private_key}) + + ## Helpers. + + @doc """ + Generates a new `TTEth.LocalWallet.t` with a random private key. + """ + def generate() do + {_pub, priv} = TTEth.new_keypair() + + %{private_key: priv} + |> new() + end +end diff --git a/lib/tt_eth/protocols/wallet.ex b/lib/tt_eth/protocols/wallet.ex new file mode 100644 index 0000000..c156d53 --- /dev/null +++ b/lib/tt_eth/protocols/wallet.ex @@ -0,0 +1,16 @@ +defprotocol TTEth.Protocols.Wallet do + @moduledoc """ + Protocol for wallet adapters. + """ + alias TTEth.Wallet + + @doc """ + Given a wallet and returns a map of attributes used to construct a `TTEth.Wallet.t()`. + """ + def new_attrs(t) + + @doc """ + Given a wallet and a hash digest and returns a signature. + """ + def sign(t, digest) +end diff --git a/lib/tt_eth/transactions/eip1559_transaction.ex b/lib/tt_eth/transactions/eip1559_transaction.ex index 9cf2243..661dd26 100644 --- a/lib/tt_eth/transactions/eip1559_transaction.ex +++ b/lib/tt_eth/transactions/eip1559_transaction.ex @@ -4,7 +4,7 @@ defmodule TTEth.Transactions.EIP1559Transaction do SEE: https://eips.ethereum.org/EIPS/eip-1559 """ - alias TTEth.{BitHelper, Secp256k1} + alias TTEth.{BitHelper, Wallet} alias TTEth.Behaviours.Transaction import BitHelper, only: [encode_unsigned: 1] @@ -53,10 +53,10 @@ defmodule TTEth.Transactions.EIP1559Transaction do This will return a binary which can then be base16 encoded etc. """ @impl Transaction - def build(%__MODULE__{} = trx, private_key), + def build(%__MODULE__{} = trx, %Wallet{} = wallet), do: trx - |> sign_transaction(private_key) + |> sign_transaction(wallet) |> serialize(_include_signature = true) |> rlp_encode() |> put_transaction_envelope(trx) @@ -88,8 +88,10 @@ defmodule TTEth.Transactions.EIP1559Transaction do @doc """ Returns a ECDSA signature (v,r,s) for a given hashed value. """ - def sign_hash(hash, private_key) do - {:ok, {<>, v}} = Secp256k1.ecdsa_sign_compact(hash, private_key) + def sign_hash(hash, %Wallet{} = wallet) do + {:ok, {<>, v}} = + wallet + |> Wallet.sign(hash) {v, r, s} end @@ -108,24 +110,22 @@ defmodule TTEth.Transactions.EIP1559Transaction do @doc """ Takes a given transaction and returns a version signed with the given private key. """ - def sign_transaction(%__MODULE__{} = trx, private_key) when is_binary(private_key) do + def sign_transaction(%__MODULE__{} = trx, %Wallet{} = wallet) do {y_parity, r, s} = trx |> transaction_hash() - |> sign_hash(private_key) + |> sign_hash(wallet) %{trx | y_parity: y_parity, r: r, s: s} end - @doc """ - Wraps the RLP encoded transaction in a transaction envelope. - SEE: https://eips.ethereum.org/EIPS/eip-2718 - """ - def put_transaction_envelope(encoded, %__MODULE__{} = trx) when is_binary(encoded), - do: <> <> encoded - ## Private. + # Wraps the RLP encoded transaction in a transaction envelope. + # SEE: https://eips.ethereum.org/EIPS/eip-2718 + defp put_transaction_envelope(encoded, %__MODULE__{} = trx) when is_binary(encoded), + do: <> <> encoded + # Optionally add the YRS values. defp maybe_add_yrs(base, %__MODULE__{} = trx, _include_vrs = true), do: diff --git a/lib/tt_eth/transactions/legacy_transaction.ex b/lib/tt_eth/transactions/legacy_transaction.ex index 5cd3071..6e1d439 100644 --- a/lib/tt_eth/transactions/legacy_transaction.ex +++ b/lib/tt_eth/transactions/legacy_transaction.ex @@ -2,13 +2,12 @@ defmodule TTEth.Transactions.LegacyTransaction do @moduledoc """ Ported from [`Blockchain`](https://hex.pm/packages/blockchain). """ - alias TTEth.{BitHelper, Secp256k1} + alias TTEth.{BitHelper, Wallet} alias TTEth.Behaviours.Transaction + import BitHelper, only: [encode_unsigned: 1] @behaviour Transaction - @type private_key :: <<_::256>> - @type hash_v :: integer() @type hash_r :: integer() @type hash_s :: integer() @@ -60,10 +59,10 @@ defmodule TTEth.Transactions.LegacyTransaction do } @impl Transaction - def build(%__MODULE__{} = trx, private_key), + def build(%__MODULE__{} = trx, %Wallet{} = wallet), do: trx - |> sign_transaction(private_key) + |> sign_transaction(wallet) |> serialize(_include_signature = true) |> rlp_encode() @@ -95,20 +94,20 @@ defmodule TTEth.Transactions.LegacyTransaction do @spec serialize(t) :: ExRLP.t() def serialize(trx, include_vrs \\ true) do base = [ - trx.nonce |> BitHelper.encode_unsigned(), - trx.gas_price |> BitHelper.encode_unsigned(), - trx.gas_limit |> BitHelper.encode_unsigned(), + trx.nonce |> encode_unsigned(), + trx.gas_price |> encode_unsigned(), + trx.gas_limit |> encode_unsigned(), trx.to, - trx.value |> BitHelper.encode_unsigned(), + trx.value |> encode_unsigned(), if(trx.to == <<>>, do: trx.init, else: trx.data) ] if include_vrs do base ++ [ - trx.v |> BitHelper.encode_unsigned(), - trx.r |> BitHelper.encode_unsigned(), - trx.s |> BitHelper.encode_unsigned() + trx.v |> encode_unsigned(), + trx.r |> encode_unsigned(), + trx.s |> encode_unsigned() ] else base @@ -122,12 +121,14 @@ defmodule TTEth.Transactions.LegacyTransaction do ## Examples - iex> LegacyTransaction.sign_hash(<<2::256>>, <<1::256>>) + iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>) + iex> LegacyTransaction.sign_hash(<<2::256>>, wallet) {28, 38938543279057362855969661240129897219713373336787331739561340553100525404231, 23772455091703794797226342343520955590158385983376086035257995824653222457926} - iex> LegacyTransaction.sign_hash(<<5::256>>, <<1::256>>) + iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>) + iex> LegacyTransaction.sign_hash(<<5::256>>, wallet) {27, 74927840775756275467012999236208995857356645681540064312847180029125478834483, 56037731387691402801139111075060162264934372456622294904359821823785637523849} @@ -135,15 +136,17 @@ defmodule TTEth.Transactions.LegacyTransaction do iex> data = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080" |> TTEth.BitHelper.from_hex iex> hash = data |> TTEth.keccak() iex> private_key = "4646464646464646464646464646464646464646464646464646464646464646" |> TTEth.BitHelper.from_hex - iex> LegacyTransaction.sign_hash(hash, private_key, 1) + iex> wallet = TTEth.Wallet.from_private_key(private_key) + iex> LegacyTransaction.sign_hash(hash, wallet, 1) { 37, 18515461264373351373200002665853028612451056578545711640558177340181847433846, 46948507304638947509940763649030358759909902576025900602547168820602576006531 } """ - @spec sign_hash(BitHelper.keccak_hash(), private_key, integer() | nil) :: + @spec sign_hash(BitHelper.keccak_hash(), Wallet.t(), integer() | nil) :: {hash_v, hash_r, hash_s} - def sign_hash(hash, private_key, chain_id \\ nil) do + def sign_hash(hash, %Wallet{} = wallet, chain_id \\ nil) do {:ok, {<>, recovery_id}} = - Secp256k1.ecdsa_sign_compact(hash, private_key) + wallet + |> Wallet.sign(hash) # Fork Ψ EIP-155 recovery_id = @@ -192,19 +195,21 @@ defmodule TTEth.Transactions.LegacyTransaction do ## Examples - iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>) + iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>) + iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, wallet) %LegacyTransaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 97037709922803580267279977200525583527127616719646548867384185721164615918250, s: 31446571475787755537574189222065166628755695553801403547291726929250860527755, to: "", v: 27, value: 5} - iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>, 1) + iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>) + iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, wallet, 1) %LegacyTransaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 25739987953128435966549144317523422635562973654702886626580606913510283002553, s: 41423569377768420285000144846773344478964141018753766296386430811329935846420, to: "", v: 38, value: 5} """ - @spec sign_transaction(__MODULE__.t(), private_key, integer() | nil) :: __MODULE__.t() - def sign_transaction(trx, private_key, chain_id \\ nil) do + @spec sign_transaction(__MODULE__.t(), Wallet.t(), integer() | nil) :: __MODULE__.t() + def sign_transaction(trx, %Wallet{} = wallet, chain_id \\ nil) do {v, r, s} = trx |> transaction_hash(chain_id) - |> sign_hash(private_key, chain_id) + |> sign_hash(wallet, chain_id) %{trx | v: v, r: r, s: s} end diff --git a/lib/tt_eth/type/signature.ex b/lib/tt_eth/type/signature.ex index df4d723..67e53ea 100644 --- a/lib/tt_eth/type/signature.ex +++ b/lib/tt_eth/type/signature.ex @@ -1,8 +1,7 @@ defmodule TTEth.Type.Signature do @moduledoc "This module is an Ecto-compatible type that can represent Ethereum signatures." use TTEth.Type, size: 65 - alias TTEth.BitHelper - alias TTEth.Secp256k1 + alias TTEth.{BitHelper, Secp256k1} import TTEth, only: [keccak: 1] @secp256k1n 115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337 diff --git a/lib/tt_eth/wallet.ex b/lib/tt_eth/wallet.ex index 793e441..9b71867 100644 --- a/lib/tt_eth/wallet.ex +++ b/lib/tt_eth/wallet.ex @@ -2,45 +2,82 @@ defmodule TTEth.Wallet do @moduledoc """ Provides a handle struct - `TTEth.Wallet.t()` for encapsulating a wallet. """ - alias TTEth.Type.{Address, PublicKey, PrivateKey} + alias TTEth.Protocols.Wallet, as: WalletProtocol @type t :: %__MODULE__{} + defstruct [ + :adapter, :address, :public_key, - :private_key, :human_address, :human_public_key, - :human_private_key + :_adapter ] + @doc """ + Looks up a wallet by name from config. + + Expects the config to look like so: + + config TTEth, + wallet_adapter: TTEth.LocalWallet, + wallets: [ + primary: "0x0aF6...0000", + ] + """ @spec named(atom) :: t() def named(name), do: :tt_eth |> Application.fetch_env!(:wallets) |> Keyword.fetch!(name) - |> from_private_key() + |> build_with_adapter!( + :tt_eth + |> Application.fetch_env!(:wallet_adapter) + ) + |> new() - @doc "Constructs a wallet from a private key." - @spec from_private_key(binary) :: t() - def from_private_key(priv) when is_binary(priv) do - raw_priv = priv |> PrivateKey.from_human!() |> PublicKey.from_private_key!() - {raw_priv, priv} |> new() - end + @doc """ + Constructs a new wallet from an underlying wallet or a random one. + """ + def new(wallet \\ TTEth.LocalWallet.generate()) when is_struct(wallet), + do: + __MODULE__ + |> struct!( + wallet + |> WalletProtocol.new_attrs() + ) - @doc "Constructs a new wallet from a keypair or a random one." - @spec new({binary, binary}) :: t() - def new({pub, priv} \\ TTEth.new_keypair()) when is_binary(pub) and is_binary(priv) do - address = pub |> Address.from_public_key!() - - struct!(%__MODULE__{}, %{ - address: address |> Address.from_human!(), - public_key: pub |> PublicKey.from_human!(), - private_key: priv |> PrivateKey.from_human!(), - human_address: address |> Address.to_human!(), - human_public_key: pub |> PublicKey.to_human!(), - human_private_key: priv |> PrivateKey.to_human!() - }) + @doc """ + Given a wallet signs a digest. + """ + def sign(%__MODULE__{_adapter: wallet}, "" <> digest), + do: + wallet + |> WalletProtocol.sign(digest) + + @doc """ + The same as `sign/2` but raises if the signing process is not successful. + """ + def sign!(%__MODULE__{} = wallet, "" <> digest) do + {:ok, ret} = wallet |> sign(digest) + ret end + + @doc """ + Given a private key returns a new `Wallet.t` struct. + """ + def from_private_key("" <> private_key), + do: + private_key + |> build_with_adapter!() + |> new() + + ## Private. + + defp build_with_adapter!(config, adapter \\ TTEth.LocalWallet), + do: + config + |> adapter.new() end diff --git a/mix.exs b/mix.exs index dd75a3a..fe86260 100644 --- a/mix.exs +++ b/mix.exs @@ -40,6 +40,14 @@ defmodule TTEth.MixProject do defp groups_for_modules(), do: [ + Behaviours: [ + TTEth.Behaviours.ChainClient, + TTEth.Behaviours.Transaction, + TTEth.Behaviours.Wallet + ], + Protocols: [ + TTEth.Protocols.Wallet + ], Types: [ TTEth.Type.Address, TTEth.Type.Hash, diff --git a/test/tt_eth/local_wallet_test.exs b/test/tt_eth/local_wallet_test.exs new file mode 100644 index 0000000..cd8322a --- /dev/null +++ b/test/tt_eth/local_wallet_test.exs @@ -0,0 +1,5 @@ +defmodule TTEth.LocalWalletTest do + use TTEth.Case + alias TTEth.LocalWallet + doctest LocalWallet, import: true +end diff --git a/test/tt_eth/transactions/eip1559_transaction_test.exs b/test/tt_eth/transactions/eip1559_transaction_test.exs index dd31a1c..1597a62 100644 --- a/test/tt_eth/transactions/eip1559_transaction_test.exs +++ b/test/tt_eth/transactions/eip1559_transaction_test.exs @@ -2,12 +2,12 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do use TTEth.Case alias TTEth.Transactions.EIP1559Transaction alias TTEth.Type.{Address, PrivateKey, PublicKey} + alias TTEth.Wallet # Polygon Mumbai. @chain_id 80001 @private_key_human "0x62aa6ec41b56439d2c5df352c45a00389cef262b3761e13c6481e35ab027d262" - @private_key @private_key_human |> PrivateKey.from_human!() @to_address_human "0x38f153fdd399ff2cf64704c6a4b16d3fd9ddcd69" @to_address @to_address_human |> Address.from_human!() # transfer(address,uint256) @@ -51,20 +51,23 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do end describe "build/2" do - setup :build_trx + setup [ + :build_trx, + :build_wallet + ] - test "builds a signed transaction", %{trx: trx} do + test "builds a signed transaction", %{trx: trx, wallet: wallet} do trx - |> EIP1559Transaction.build(@private_key) + |> EIP1559Transaction.build(wallet) |> encode_and_pad() |> assert_match(@valid_transaction_data) end - test "from address is correct when checking signature", %{trx: trx} do + test "from address is correct when checking signature", %{trx: trx, wallet: wallet} do # Build the trx_data but randomize the nonce. built_trx_data = %{trx | nonce: Enum.random(10..100)} - |> EIP1559Transaction.build(@private_key) + |> EIP1559Transaction.build(wallet) |> encode_and_pad() # Decode the transaction data. @@ -117,6 +120,9 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do } } + defp build_wallet(_), + do: %{wallet: @private_key_human |> Wallet.from_private_key()} + ## Helpers. defp encode_and_pad(bin), diff --git a/test/tt_eth/wallet_test.exs b/test/tt_eth/wallet_test.exs index 21de7bb..de83b0a 100644 --- a/test/tt_eth/wallet_test.exs +++ b/test/tt_eth/wallet_test.exs @@ -1,5 +1,6 @@ defmodule TTEth.WalletTest do use TTEth.Case, async: true + alias TTEth.LocalWallet @human_private_key "0xfa015243f2e6d8694ab037a7987dc73b1630fc8cb1ce82860344684c15d24026" @@ -25,13 +26,6 @@ defmodule TTEth.WalletTest do |> Wallet.from_private_key() |> assert_match_ternary_wallet() end - - test "reconstructs everything from a binary private key" do - @human_private_key - |> TTEth.Type.PrivateKey.from_human!() - |> Wallet.from_private_key() - |> assert_match_ternary_wallet() - end end describe "new/0+1" do @@ -40,7 +34,7 @@ defmodule TTEth.WalletTest do end test "generates a wallet deterministically given a keypair" do - kp = TTEth.new_keypair() + kp = LocalWallet.generate() assert Wallet.new(kp) == Wallet.new(kp) end end @@ -48,10 +42,11 @@ defmodule TTEth.WalletTest do ## Private. defp assert_match_ternary_wallet(wallet) do - private_key = @human_private_key |> TTEth.Type.PrivateKey.from_human!() human_address = "0x0aF6b8a8E5D56F0ab74D47Ac446EEa46817F32bC" address = human_address |> TTEth.Type.Address.from_human!() + private_key = @human_private_key |> TTEth.Type.PrivateKey.from_human!() + human_public_key = "0x58be6efb58e39ce4b5d1ca552d80f8c9009dfecec0e5a31fc8d22ee866320c506be5" <> "8730c77623df9862d8041c1bdef8a031e5d38a1ac1b83d053277391f974c" @@ -60,12 +55,15 @@ defmodule TTEth.WalletTest do wallet |> assert_match(%Wallet{ + adapter: LocalWallet, + address: ^address, + public_key: ^public_key, human_address: ^human_address, - human_private_key: @human_private_key, human_public_key: ^human_public_key, - address: ^address, - private_key: ^private_key, - public_key: ^public_key + _adapter: %LocalWallet{ + private_key: ^private_key, + human_private_key: @human_private_key + } }) end end diff --git a/test/tt_eth_test.exs b/test/tt_eth_test.exs index d6b9d46..06c4f0c 100644 --- a/test/tt_eth_test.exs +++ b/test/tt_eth_test.exs @@ -59,18 +59,16 @@ defmodule TTEthTest do tx_data = to - |> TTEth.ChainClient.build_tx_data(abi_data, wallet.private_key, nonce_dec, - chain_id: @chain_id - ) + |> TTEth.ChainClient.build_tx_data(abi_data, wallet, nonce_dec, chain_id: @chain_id) ChainClientMock # We don't care about this in this test. |> expect(:eth_get_transaction_count, fn _, _ -> {:ok, nonce_hex} end) # We want to make sure that the transaction building function was called with the correct params. - |> expect(:build_tx_data, fn to_, abi_data_, private_key_, nonce_, opts_ -> + |> expect(:build_tx_data, fn to_, abi_data_, wallet_, nonce_, opts_ -> assert to_ == to assert abi_data == abi_data_ - assert private_key_ == wallet.private_key + assert wallet_ == wallet assert nonce_ == nonce_dec assert opts_ == [chain_id: @chain_id] tx_data