diff --git a/lib/ethers.ex b/lib/ethers.ex index a42ad20..8c49ee6 100644 --- a/lib/ethers.ex +++ b/lib/ethers.ex @@ -459,8 +459,8 @@ defmodule Ethers do - `:address`: Indicates event emitter contract address. (nil means all contracts) - `:rpc_client`: The RPC Client to use. It should implement ethereum jsonRPC API. default: Ethereumex.HttpClient - `:rpc_opts`: Extra options to pass to rpc_client. (Like timeout, Server URL, etc.) - - `:fromBlock`: Minimum block number of logs to filter. - - `:toBlock`: Maximum block number of logs to filter. + - `:fromBlock` | `:from_block`: Minimum block number of logs to filter. + - `:toBlock` | `:to_block`: Maximum block number of logs to filter. """ @spec get_logs(map(), Keyword.t()) :: {:ok, [Event.t()]} | {:error, atom()} def get_logs(event_filter, overrides \\ []) do @@ -564,19 +564,11 @@ defmodule Ethers do @doc false @spec rpc_client() :: atom() - def rpc_client, do: Application.get_env(:ethers, :rpc_client, Ethereumex.HttpClient) + defdelegate rpc_client(), to: Ethers.RpcClient @doc false @spec get_rpc_client(Keyword.t()) :: {atom(), Keyword.t()} - def get_rpc_client(opts) do - module = - case Keyword.fetch(opts, :rpc_client) do - {:ok, module} when is_atom(module) -> module - :error -> Ethers.rpc_client() - end - - {module, Keyword.get(opts, :rpc_opts, [])} - end + defdelegate get_rpc_client(opts), to: Ethers.RpcClient defp pre_process(tx_data, overrides, :call = _action, _opts) do {block, overrides} = Keyword.pop(overrides, :block, "latest") @@ -658,7 +650,9 @@ defmodule Ethers do event_filter |> EventFilter.to_map(overrides) |> ensure_hex_value(:fromBlock) + |> ensure_hex_value(:from_block) |> ensure_hex_value(:toBlock) + |> ensure_hex_value(:to_block) {:ok, log_params} end diff --git a/lib/ethers/rpc_client.ex b/lib/ethers/rpc_client.ex new file mode 100644 index 0000000..c1b2922 --- /dev/null +++ b/lib/ethers/rpc_client.ex @@ -0,0 +1,25 @@ +defmodule Ethers.RpcClient do + @moduledoc false + + @doc false + @spec rpc_client() :: atom() + def rpc_client do + case Application.get_env(:ethers, :rpc_client, Ethereumex.HttpClient) do + Ethereumex.HttpClient -> Ethers.RpcClient.EthereumexHttpClient + module when is_atom(module) -> module + _ -> raise ArgumentError, "Invalid ethers configuration. :rpc_client must be a module" + end + end + + @doc false + @spec get_rpc_client(Keyword.t()) :: {atom(), Keyword.t()} + def get_rpc_client(opts) do + module = + case Keyword.fetch(opts, :rpc_client) do + {:ok, module} when is_atom(module) -> module + :error -> Ethers.rpc_client() + end + + {module, Keyword.get(opts, :rpc_opts, [])} + end +end diff --git a/lib/ethers/rpc_client/adapter.ex b/lib/ethers/rpc_client/adapter.ex new file mode 100644 index 0000000..550366f --- /dev/null +++ b/lib/ethers/rpc_client/adapter.ex @@ -0,0 +1,35 @@ +defmodule Ethers.RpcClient.Adapter do + @moduledoc false + + @type error :: {:error, map() | binary() | atom()} + + @callback batch_request([{atom(), list(boolean() | binary())}], keyword()) :: + {:ok, [any()]} | error + + @callback eth_block_number(keyword()) :: {:ok, binary()} | error() + + @callback eth_call(map(), binary(), keyword()) :: {:ok, binary()} | error() + + @callback eth_estimate_gas(map(), keyword()) :: {:ok, binary()} | error() + + @callback eth_gas_price(keyword()) :: {:ok, binary()} | error() + + @callback eth_get_balance(binary(), binary(), keyword()) :: {:ok, binary()} | error() + + @callback eth_get_block_by_number(binary() | non_neg_integer(), boolean(), keyword()) :: + {:ok, map()} | error() + + @callback eth_get_transaction_by_hash(binary(), keyword()) :: {:ok, map()} | error() + + @callback eth_get_transaction_count(binary(), binary(), keyword()) :: {:ok, binary()} | error() + + @callback eth_get_transaction_receipt(binary(), keyword()) :: {:ok, map()} | error() + + @callback eth_max_priority_fee_per_gas(keyword()) :: {:ok, binary()} | error() + + @callback eth_get_logs(map(), keyword()) :: {:ok, [binary()] | [map()]} | error() + + @callback eth_send_transaction(map(), keyword()) :: {:ok, binary()} | error() + + @callback eth_send_raw_transaction(binary(), keyword()) :: {:ok, binary()} | error() +end diff --git a/lib/ethers/rpc_client/ethereumex_http_client.ex b/lib/ethers/rpc_client/ethereumex_http_client.ex new file mode 100644 index 0000000..995c49e --- /dev/null +++ b/lib/ethers/rpc_client/ethereumex_http_client.ex @@ -0,0 +1,38 @@ +defmodule Ethers.RpcClient.EthereumexHttpClient do + @moduledoc false + + alias Ethers.RpcClient.Adapter + + @behaviour Ethers.RpcClient.Adapter + + @exclude_delegation [:eth_get_logs] + + for {func, arity} <- Adapter.behaviour_info(:callbacks), func not in @exclude_delegation do + args = Macro.generate_arguments(arity - 1, __MODULE__) + + @impl true + def unquote(func)(unquote_splicing(args), opts \\ []) do + apply(Ethereumex.HttpClient, unquote(func), [unquote_splicing(args), opts]) + end + end + + @impl true + def eth_get_logs(params, opts \\ []) do + params + |> replace_key(:from_block, :fromBlock) + |> replace_key(:to_block, :toBlock) + |> Ethereumex.HttpClient.eth_get_logs(opts) + end + + defp replace_key(map, ethers_key, ethereumex_key) do + case Map.fetch(map, ethers_key) do + {:ok, value} -> + map + |> Map.put(ethereumex_key, value) + |> Map.delete(ethers_key) + + :error -> + map + end + end +end diff --git a/test/ethers/counter_contract_test.exs b/test/ethers/counter_contract_test.exs index 068e322..cc3ff0e 100644 --- a/test/ethers/counter_contract_test.exs +++ b/test/ethers/counter_contract_test.exs @@ -274,7 +274,7 @@ defmodule Ethers.CounterContractTest do assert String.valid?(block_hash) end - test "can filter logs with fromBlock and toBlock options", %{address: address} do + test "can filter logs with from_block and to_block options", %{address: address} do {:ok, tx_hash} = CounterContract.set(101) |> Ethers.send(from: @from, to: address) wait_for_transaction!(tx_hash) @@ -285,8 +285,29 @@ defmodule Ethers.CounterContractTest do assert [] == Ethers.get_logs!(filter, - fromBlock: current_block_number - 1, - toBlock: current_block_number - 1 + from_block: current_block_number - 1, + to_block: current_block_number - 1 + ) + + assert [ + %Ethers.Event{ + address: ^address, + topics: ["SetCalled(uint256,uint256)", 100], + data: [101], + data_raw: "0x0000000000000000000000000000000000000000000000000000000000000065", + log_index: 0, + removed: false, + topics_raw: [ + "0x9db4e91e99652c2cf1713076f100fca6a4f5b81f166bce406ff2b3012694f49f", + "0x0000000000000000000000000000000000000000000000000000000000000064" + ], + transaction_hash: ^tx_hash, + transaction_index: 0 + } + ] = + Ethers.get_logs!(filter, + from_block: current_block_number - 1, + to_block: current_block_number ) end end