Skip to content

Commit 033ab32

Browse files
authored
Fix address parsing and display errors (#229)
1 parent e850b20 commit 033ab32

File tree

7 files changed

+82
-24
lines changed

7 files changed

+82
-24
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## UNRELEASED
4+
5+
### Breaking Changes
6+
7+
- `Ethers.Utils.human_arg/2` now returns checksummed addresses instead of lowercase addresses for better EIP-55 compliance
8+
9+
### Bug Fixes
10+
11+
- Fix `Ethers.Utils.human_arg/2` incorrectly handling 20-byte binary addresses that start with bytes `0x30` and `0x78` (which represent the string "0x")
12+
313
## 0.6.9 (2025-10-16)
414

515
### Bug Fixes

lib/ethers/utils.ex

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ defmodule Ethers.Utils do
192192
<<192, 42, 170, 57, 178, 35, 254, 141, 10, 14, 92, 79, 39, 234, 217, 8, 60, 117, 108, 194>>
193193
"""
194194
@spec prepare_arg(term(), ABI.FunctionSelector.type()) :: term()
195-
def prepare_arg("0x" <> _ = argument, :address), do: hex_decode!(argument)
195+
def prepare_arg(<<"0x", address::binary-40>>, :address), do: hex_decode!(address)
196196
def prepare_arg(arguments, {:array, type}), do: Enum.map(arguments, &prepare_arg(&1, type))
197197
def prepare_arg(arguments, {:array, type, _}), do: Enum.map(arguments, &prepare_arg(&1, type))
198198

@@ -212,14 +212,17 @@ defmodule Ethers.Utils do
212212
## Examples
213213
iex> Ethers.Utils.human_arg(<<192, 42, 170, 57, 178, 35, 254, 141, 10, 14, 92, 79, 39,
214214
...> 234, 217, 8, 60, 117, 108, 194>>, :address)
215-
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
215+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
216216
217217
iex> Ethers.Utils.human_arg("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", :address)
218-
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
218+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
219219
"""
220220
@spec human_arg(term(), ABI.FunctionSelector.type()) :: term()
221-
def human_arg("0x" <> _ = argument, :address), do: argument
222-
def human_arg(argument, :address), do: hex_encode(argument)
221+
def human_arg(<<"0x", _::binary-40>> = address, :address), do: to_checksum_address(address)
222+
def human_arg(<<address::binary-20>>, :address), do: to_checksum_address(address)
223+
224+
def human_arg(invalid, :address),
225+
do: raise(ArgumentError, "Invalid address: #{inspect(invalid)}")
223226

224227
def human_arg(arguments, {:array, type}), do: Enum.map(arguments, &human_arg(&1, type))
225228
def human_arg(arguments, {:array, type, _}), do: Enum.map(arguments, &human_arg(&1, type))
@@ -255,12 +258,15 @@ defmodule Ethers.Utils do
255258
iex> Ethers.Utils.to_checksum_address("0XDE709F2102306220921060314715629080e2Fb77", 30)
256259
"0xDe709F2102306220921060314715629080e2FB77"
257260
"""
258-
@spec to_checksum_address(Ethers.Types.t_address(), pos_integer() | nil) ::
261+
@spec to_checksum_address(Ethers.Types.t_address() | <<_::320>>, pos_integer() | nil) ::
259262
Ethers.Types.t_address()
260263
def to_checksum_address(address, chain_id \\ nil)
261264

262-
def to_checksum_address("0x" <> address, chain_id), do: to_checksum_address(address, chain_id)
263-
def to_checksum_address("0X" <> address, chain_id), do: to_checksum_address(address, chain_id)
265+
def to_checksum_address(<<"0x", address::binary-40>>, chain_id),
266+
do: to_checksum_address(address, chain_id)
267+
268+
def to_checksum_address(<<"0X", address::binary-40>>, chain_id),
269+
do: to_checksum_address(address, chain_id)
264270

265271
def to_checksum_address(<<address_bin::binary-20>>, chain_id),
266272
do: hex_encode(address_bin, false) |> to_checksum_address(chain_id)
@@ -341,20 +347,20 @@ defmodule Ethers.Utils do
341347
public_key_to_address(public_key, use_checksum_address)
342348
end
343349

344-
unless Code.ensure_loaded?(Ethers.secp256k1_module()) do
345-
def public_key_to_address(<<pre, _::binary-32>> = compressed, _use_checksum_address)
350+
if Code.ensure_loaded?(Ethers.secp256k1_module()) do
351+
def public_key_to_address(<<pre, _::binary-32>> = compressed, use_checksum_address)
352+
when pre in [2, 3] do
353+
case Ethers.secp256k1_module().public_key_decompress(compressed) do
354+
{:ok, public_key} -> public_key_to_address(public_key, use_checksum_address)
355+
error -> raise ArgumentError, "Invalid compressed public key #{inspect(error)}"
356+
end
357+
end
358+
else
359+
def public_key_to_address(<<pre, _::binary-32>> = _compressed, _use_checksum_address)
346360
when pre in [2, 3],
347361
do: raise("secp256k1 module not loaded")
348362
end
349363

350-
def public_key_to_address(<<pre, _::binary-32>> = compressed, use_checksum_address)
351-
when pre in [2, 3] do
352-
case Ethers.secp256k1_module().public_key_decompress(compressed) do
353-
{:ok, public_key} -> public_key_to_address(public_key, use_checksum_address)
354-
error -> raise ArgumentError, "Invalid compressed public key #{inspect(error)}"
355-
end
356-
end
357-
358364
def public_key_to_address("0x" <> _ = key, use_checksum_address) do
359365
key
360366
|> hex_decode!()

test/ethers/event_mixed_index_contract_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Ethers.EventMixedIndexContractTest do
1010

1111
alias Ethers.Contract.Test.EventMixedIndexContract
1212

13-
@from "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
13+
@from "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
1414

1515
describe "event filters" do
1616
test "works with mixed indexed events" do
@@ -76,15 +76,15 @@ defmodule Ethers.EventMixedIndexContractTest do
7676
end
7777

7878
test "inspect returns correct value" do
79-
assert ~s'#Ethers.EventFilter<event Transfer(uint256 amount, address indexed sender "0x90f8bf6a479f320ead074411a4b0e7944ea80000", bool isFinal, address indexed receiver "0x90f8bf6a479f320ead074411a4b0e7944ea80001")>' ==
79+
assert ~s'#Ethers.EventFilter<event Transfer(uint256 amount, address indexed sender "0x90F8BF6A479f320EAd074411a4b0E7944ea80000", bool isFinal, address indexed receiver "0x90f8Bf6A479f320eaD074411a4B0e7944ea80001")>' ==
8080
inspect(
8181
EventMixedIndexContract.EventFilters.transfer(
8282
"0x90f8bf6a479f320ead074411a4b0e7944ea80000",
8383
"0x90f8bf6a479f320ead074411a4b0e7944ea80001"
8484
)
8585
)
8686

87-
assert ~s'#Ethers.EventFilter<event Transfer(uint256 amount, address indexed sender any, bool isFinal, address indexed receiver "0x90f8bf6a479f320ead074411a4b0e7944ea80001")>' ==
87+
assert ~s'#Ethers.EventFilter<event Transfer(uint256 amount, address indexed sender any, bool isFinal, address indexed receiver "0x90f8Bf6A479f320eaD074411a4B0e7944ea80001")>' ==
8888
inspect(
8989
EventMixedIndexContract.EventFilters.transfer(
9090
nil,

test/ethers/owner_contract_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule Ethers.OwnerContractTest do
1212
alias Ethers.Contract.Test.OwnerContract
1313

1414
@from "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
15-
@sample_address "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
15+
@sample_address "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
1616

1717
test "can deploy and get owner" do
1818
encoded_constructor = OwnerContract.constructor(@sample_address)

test/ethers/registry_contract_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Ethers.RegistryContractTest do
1111

1212
alias Ethers.Contract.Test.RegistryContract
1313

14-
@from "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
14+
@from "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
1515
@from1 "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
1616
@from2 "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"
1717

test/ethers/types_contract_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule Ethers.TypesContractTest do
1212
alias Ethers.Contract.Test.TypesContract
1313

1414
@from "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
15-
@sample_address "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
15+
@sample_address "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
1616

1717
setup_all :deploy_types_contract
1818

test/ethers/utils_test.exs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,46 @@ defmodule Ethers.UtilsTest do
161161
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
162162
end
163163
end
164+
165+
describe "human_arg/2" do
166+
test "handles 20-byte binary address" do
167+
# Regression test: ensure binary addresses are properly converted
168+
binary_address =
169+
<<48, 120, 170, 17, 101, 240, 156, 228, 62, 76, 75, 122, 119, 72, 248, 105, 128, 216, 172,
170+
54>>
171+
172+
result = Ethers.Utils.human_arg(binary_address, :address)
173+
174+
assert result == "0x3078aA1165F09ce43E4C4B7a7748f86980d8AC36"
175+
assert String.starts_with?(result, "0x")
176+
assert String.length(result) == 42
177+
end
178+
179+
test "handles random 20-byte binary address" do
180+
# Regression test: ensure any 20-byte binary is properly converted
181+
binary_address =
182+
<<123, 45, 67, 89, 12, 34, 56, 78, 90, 11, 22, 33, 44, 55, 66, 77, 88, 99, 111, 222>>
183+
184+
result = Ethers.Utils.human_arg(binary_address, :address)
185+
186+
assert result == "0x7B2d43590c22384e5A0B16212c37424D58636FDE"
187+
assert String.starts_with?(result, "0x")
188+
assert String.length(result) == 42
189+
end
190+
191+
test "handles hex string address" do
192+
# Ensure hex string addresses are checksummed
193+
hex_address = "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"
194+
195+
result = Ethers.Utils.human_arg(hex_address, :address)
196+
197+
assert result == "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
198+
end
199+
200+
test "raises on invalid address" do
201+
assert_raise ArgumentError, ~r/Invalid address/, fn ->
202+
Ethers.Utils.human_arg("invalid_address", :address)
203+
end
204+
end
205+
end
164206
end

0 commit comments

Comments
 (0)