From b49a30890f40a21fdac10c9f05843b51dd889f65 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Sat, 1 Aug 2026 22:10:18 +0800 Subject: [PATCH 1/2] Fix eth_getStorageAt batch calls sending padded block QUANTITY hex. get_storage_many bypassed normalize_args and encoded blocks like 0x02f1cf00, which Base RPC rejects as -32602 invalid params and crashed EdgeV2 connections. Co-authored-by: Cursor --- lib/remote_chain/rpc_cache.ex | 24 ++++++++++++++---------- test/remote_chain/rpc_cache_test.exs | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 test/remote_chain/rpc_cache_test.exs diff --git a/lib/remote_chain/rpc_cache.ex b/lib/remote_chain/rpc_cache.ex index 43ad192..633fa4b 100644 --- a/lib/remote_chain/rpc_cache.ex +++ b/lib/remote_chain/rpc_cache.ex @@ -88,7 +88,7 @@ defmodule RemoteChain.RPCCache do # for storage requests we use the last change block as the base # any contract not using change tracking will suffer 240 blocks (one hour) of caching block = get_last_change(chain, address, block) - rpc!(chain, "eth_getStorageAt", [address, slot, Base16.encode(block, false)]) + rpc!(chain, "eth_getStorageAt", [address, slot, block]) end def get_storage_many(chain, address, slots, block \\ "latest") do @@ -102,7 +102,8 @@ defmodule RemoteChain.RPCCache do cache_results = Enum.map(slots, fn slot -> - {:rpc, "eth_getStorageAt", [address, slot, Base16.encode(block, false)]} + # batch_call skips rpc/3, so normalize here (QUANTITY must have no leading zeros) + {:rpc, "eth_getStorageAt", normalize_args(chain, "eth_getStorageAt", [address, slot, block])} end) |> Enum.map(fn rpc = {:rpc, method, params} -> with %{"result" => result} <- Cache.get(cache, {chain, method, params}) do @@ -176,34 +177,37 @@ defmodule RemoteChain.RPCCache do def get_balance(chain, address, block \\ "latest"), do: rpc!(chain, "eth_getBalance", [address, block]) - defp normalize_args(chain, "eth_getBalance", [address, block]) do + # Also used by get_storage_many before batch_call (which skips rpc/3). + @doc false + def normalize_args(chain, "eth_getBalance", [address, block]) do [address, normalize_block(chain, block)] end - defp normalize_args(chain, "eth_getTransactionCount", [address, block]) do + def normalize_args(chain, "eth_getTransactionCount", [address, block]) do [address, normalize_block(chain, block)] end - defp normalize_args(chain, "eth_getBlockByNumber", [block, with_transactions]) do + def normalize_args(chain, "eth_getBlockByNumber", [block, with_transactions]) do [normalize_block(chain, block), with_transactions] end - defp normalize_args(chain, "eth_call", [opts, block]) do + def normalize_args(chain, "eth_call", [opts, block]) do [opts, normalize_block(chain, block)] end - defp normalize_args(chain, "eth_getCode", [address, block]) do + def normalize_args(chain, "eth_getCode", [address, block]) do [address, normalize_block(chain, block)] end - defp normalize_args(chain, "eth_getStorageAt", [address, slot, block]) do + def normalize_args(chain, "eth_getStorageAt", [address, slot, block]) do [address, slot, normalize_block(chain, block)] end - defp normalize_args(_chain, _method, args) do + def normalize_args(_chain, _method, args) do args end + # JSON-RPC QUANTITY: no leading zeros. Padded "0x02f1cf00" is rejected as -32602 on Base. defp normalize_block(chain, block) do Base16.encode(resolve_block(chain, block), short: true) end @@ -253,7 +257,7 @@ defmodule RemoteChain.RPCCache do rpc!(chain, "eth_getStorageAt", [ address, "0x1e4717b2dc5dfd7f487f2043bfe9999372d693bf4d9c51b5b84f1377939cd487", - Base16.encode(block, false) + block ]) |> Base16.decode_int() |> case do diff --git a/test/remote_chain/rpc_cache_test.exs b/test/remote_chain/rpc_cache_test.exs new file mode 100644 index 0000000..4c6d64e --- /dev/null +++ b/test/remote_chain/rpc_cache_test.exs @@ -0,0 +1,20 @@ +# Diode Server +# Copyright 2021-2024 Diode +# Licensed under the Diode License, Version 1.1 +defmodule RemoteChain.RPCCacheTest do + use ExUnit.Case, async: true + + describe "normalize_args/3 for eth_getStorageAt" do + test "uses short QUANTITY block encoding (Base rejects padded hex like 0x02f1cf00)" do + # get_storage_many batch_call skips rpc/3; it must still run normalize_args. + address = "0xda92764bb12e91010d132bcdd8e4a0270ee25fc9" + slot = "0xc9f6766ee659edc5eddd04b71e82c56aa865e95b4ae66f7ebe9a16071844b44a" + + assert RemoteChain.RPCCache.normalize_args(Chains.Anvil, "eth_getStorageAt", [ + address, + slot, + 49_401_600 + ]) == [address, slot, "0x2f1cf00"] + end + end +end From 62a590239378e2ae94d99059a3aa2b86ac791a76 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Sat, 1 Aug 2026 22:25:22 +0800 Subject: [PATCH 2/2] Fix mix format failures blocking CI. Co-authored-by: Cursor --- lib/remote_chain/rpc_cache.ex | 3 ++- scripts/bns.exs | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/remote_chain/rpc_cache.ex b/lib/remote_chain/rpc_cache.ex index 633fa4b..adb5f90 100644 --- a/lib/remote_chain/rpc_cache.ex +++ b/lib/remote_chain/rpc_cache.ex @@ -103,7 +103,8 @@ defmodule RemoteChain.RPCCache do cache_results = Enum.map(slots, fn slot -> # batch_call skips rpc/3, so normalize here (QUANTITY must have no leading zeros) - {:rpc, "eth_getStorageAt", normalize_args(chain, "eth_getStorageAt", [address, slot, block])} + {:rpc, "eth_getStorageAt", + normalize_args(chain, "eth_getStorageAt", [address, slot, block])} end) |> Enum.map(fn rpc = {:rpc, method, params} -> with %{"result" => result} <- Cache.get(cache, {chain, method, params}) do diff --git a/scripts/bns.exs b/scripts/bns.exs index ee24bf8..1b3ca08 100644 --- a/scripts/bns.exs +++ b/scripts/bns.exs @@ -212,9 +212,7 @@ for chunk <- missing do ) |> Base16.decode_int() - IO.puts( - "Wallet: #{Base16.encode(Wallet.address!(wallet))} Nonce: #{nonce} Balance: #{balance}" - ) + IO.puts("Wallet: #{Base16.encode(Wallet.address!(wallet))} Nonce: #{nonce} Balance: #{balance}") {txs, _next_nonce} = Enum.reduce(chunk, {[], nonce}, fn {name, owner, _identity, _deployed?, slot}, {acc, n} ->