Skip to content

Commit 5e62d9d

Browse files
joaop21alisinabh
andauthored
Add Adapter for RPC client (#152)
* add an adapter interface * add rpc_client module * add ethereumex http client adapter implementation * apply changes to ethers facade * backwards compatibility * backwards compatibility on the rpc_client configuration * Add missing rpc calls and default opt * Add test and improve naming * Add moduledoc false to private modules --------- Co-authored-by: Alisina Bahadori <alisina.bm@gmail.com>
1 parent c3315ab commit 5e62d9d

File tree

5 files changed

+128
-15
lines changed

5 files changed

+128
-15
lines changed

lib/ethers.ex

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ defmodule Ethers do
459459
- `:address`: Indicates event emitter contract address. (nil means all contracts)
460460
- `:rpc_client`: The RPC Client to use. It should implement ethereum jsonRPC API. default: Ethereumex.HttpClient
461461
- `:rpc_opts`: Extra options to pass to rpc_client. (Like timeout, Server URL, etc.)
462-
- `:fromBlock`: Minimum block number of logs to filter.
463-
- `:toBlock`: Maximum block number of logs to filter.
462+
- `:fromBlock` | `:from_block`: Minimum block number of logs to filter.
463+
- `:toBlock` | `:to_block`: Maximum block number of logs to filter.
464464
"""
465465
@spec get_logs(map(), Keyword.t()) :: {:ok, [Event.t()]} | {:error, atom()}
466466
def get_logs(event_filter, overrides \\ []) do
@@ -564,19 +564,11 @@ defmodule Ethers do
564564

565565
@doc false
566566
@spec rpc_client() :: atom()
567-
def rpc_client, do: Application.get_env(:ethers, :rpc_client, Ethereumex.HttpClient)
567+
defdelegate rpc_client(), to: Ethers.RpcClient
568568

569569
@doc false
570570
@spec get_rpc_client(Keyword.t()) :: {atom(), Keyword.t()}
571-
def get_rpc_client(opts) do
572-
module =
573-
case Keyword.fetch(opts, :rpc_client) do
574-
{:ok, module} when is_atom(module) -> module
575-
:error -> Ethers.rpc_client()
576-
end
577-
578-
{module, Keyword.get(opts, :rpc_opts, [])}
579-
end
571+
defdelegate get_rpc_client(opts), to: Ethers.RpcClient
580572

581573
defp pre_process(tx_data, overrides, :call = _action, _opts) do
582574
{block, overrides} = Keyword.pop(overrides, :block, "latest")
@@ -658,7 +650,9 @@ defmodule Ethers do
658650
event_filter
659651
|> EventFilter.to_map(overrides)
660652
|> ensure_hex_value(:fromBlock)
653+
|> ensure_hex_value(:from_block)
661654
|> ensure_hex_value(:toBlock)
655+
|> ensure_hex_value(:to_block)
662656

663657
{:ok, log_params}
664658
end

lib/ethers/rpc_client.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule Ethers.RpcClient do
2+
@moduledoc false
3+
4+
@doc false
5+
@spec rpc_client() :: atom()
6+
def rpc_client do
7+
case Application.get_env(:ethers, :rpc_client, Ethereumex.HttpClient) do
8+
Ethereumex.HttpClient -> Ethers.RpcClient.EthereumexHttpClient
9+
module when is_atom(module) -> module
10+
_ -> raise ArgumentError, "Invalid ethers configuration. :rpc_client must be a module"
11+
end
12+
end
13+
14+
@doc false
15+
@spec get_rpc_client(Keyword.t()) :: {atom(), Keyword.t()}
16+
def get_rpc_client(opts) do
17+
module =
18+
case Keyword.fetch(opts, :rpc_client) do
19+
{:ok, module} when is_atom(module) -> module
20+
:error -> Ethers.rpc_client()
21+
end
22+
23+
{module, Keyword.get(opts, :rpc_opts, [])}
24+
end
25+
end

lib/ethers/rpc_client/adapter.ex

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
defmodule Ethers.RpcClient.Adapter do
2+
@moduledoc false
3+
4+
@type error :: {:error, map() | binary() | atom()}
5+
6+
@callback batch_request([{atom(), list(boolean() | binary())}], keyword()) ::
7+
{:ok, [any()]} | error
8+
9+
@callback eth_block_number(keyword()) :: {:ok, binary()} | error()
10+
11+
@callback eth_call(map(), binary(), keyword()) :: {:ok, binary()} | error()
12+
13+
@callback eth_estimate_gas(map(), keyword()) :: {:ok, binary()} | error()
14+
15+
@callback eth_gas_price(keyword()) :: {:ok, binary()} | error()
16+
17+
@callback eth_get_balance(binary(), binary(), keyword()) :: {:ok, binary()} | error()
18+
19+
@callback eth_get_block_by_number(binary() | non_neg_integer(), boolean(), keyword()) ::
20+
{:ok, map()} | error()
21+
22+
@callback eth_get_transaction_by_hash(binary(), keyword()) :: {:ok, map()} | error()
23+
24+
@callback eth_get_transaction_count(binary(), binary(), keyword()) :: {:ok, binary()} | error()
25+
26+
@callback eth_get_transaction_receipt(binary(), keyword()) :: {:ok, map()} | error()
27+
28+
@callback eth_max_priority_fee_per_gas(keyword()) :: {:ok, binary()} | error()
29+
30+
@callback eth_get_logs(map(), keyword()) :: {:ok, [binary()] | [map()]} | error()
31+
32+
@callback eth_send_transaction(map(), keyword()) :: {:ok, binary()} | error()
33+
34+
@callback eth_send_raw_transaction(binary(), keyword()) :: {:ok, binary()} | error()
35+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule Ethers.RpcClient.EthereumexHttpClient do
2+
@moduledoc false
3+
4+
alias Ethers.RpcClient.Adapter
5+
6+
@behaviour Ethers.RpcClient.Adapter
7+
8+
@exclude_delegation [:eth_get_logs]
9+
10+
for {func, arity} <- Adapter.behaviour_info(:callbacks), func not in @exclude_delegation do
11+
args = Macro.generate_arguments(arity - 1, __MODULE__)
12+
13+
@impl true
14+
def unquote(func)(unquote_splicing(args), opts \\ []) do
15+
apply(Ethereumex.HttpClient, unquote(func), [unquote_splicing(args), opts])
16+
end
17+
end
18+
19+
@impl true
20+
def eth_get_logs(params, opts \\ []) do
21+
params
22+
|> replace_key(:from_block, :fromBlock)
23+
|> replace_key(:to_block, :toBlock)
24+
|> Ethereumex.HttpClient.eth_get_logs(opts)
25+
end
26+
27+
defp replace_key(map, ethers_key, ethereumex_key) do
28+
case Map.fetch(map, ethers_key) do
29+
{:ok, value} ->
30+
map
31+
|> Map.put(ethereumex_key, value)
32+
|> Map.delete(ethers_key)
33+
34+
:error ->
35+
map
36+
end
37+
end
38+
end

test/ethers/counter_contract_test.exs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ defmodule Ethers.CounterContractTest do
274274
assert String.valid?(block_hash)
275275
end
276276

277-
test "can filter logs with fromBlock and toBlock options", %{address: address} do
277+
test "can filter logs with from_block and to_block options", %{address: address} do
278278
{:ok, tx_hash} = CounterContract.set(101) |> Ethers.send(from: @from, to: address)
279279

280280
wait_for_transaction!(tx_hash)
@@ -285,8 +285,29 @@ defmodule Ethers.CounterContractTest do
285285

286286
assert [] ==
287287
Ethers.get_logs!(filter,
288-
fromBlock: current_block_number - 1,
289-
toBlock: current_block_number - 1
288+
from_block: current_block_number - 1,
289+
to_block: current_block_number - 1
290+
)
291+
292+
assert [
293+
%Ethers.Event{
294+
address: ^address,
295+
topics: ["SetCalled(uint256,uint256)", 100],
296+
data: [101],
297+
data_raw: "0x0000000000000000000000000000000000000000000000000000000000000065",
298+
log_index: 0,
299+
removed: false,
300+
topics_raw: [
301+
"0x9db4e91e99652c2cf1713076f100fca6a4f5b81f166bce406ff2b3012694f49f",
302+
"0x0000000000000000000000000000000000000000000000000000000000000064"
303+
],
304+
transaction_hash: ^tx_hash,
305+
transaction_index: 0
306+
}
307+
] =
308+
Ethers.get_logs!(filter,
309+
from_block: current_block_number - 1,
310+
to_block: current_block_number
290311
)
291312
end
292313
end

0 commit comments

Comments
 (0)