Skip to content

Commit 28e3d53

Browse files
alexAlex Kwiatkowski
authored andcommitted
Add Address struct with util helpers
1 parent ad11e93 commit 28e3d53

File tree

4 files changed

+127
-27
lines changed

4 files changed

+127
-27
lines changed

lib/exw3/address.ex

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule ExW3.Address do
2+
@type t :: %__MODULE__{bytes: binary}
3+
4+
defstruct ~w[bytes]a
5+
6+
@spec from_bytes(binary) :: t
7+
def from_bytes(bytes) do
8+
%__MODULE__{bytes: bytes}
9+
end
10+
11+
@spec from_hex(String.t()) :: t
12+
def from_hex(address) do
13+
case address do
14+
"0x" <> a ->
15+
from_hex(a)
16+
17+
a ->
18+
bytes = a |> String.downcase() |> Base.decode16!(case: :lower)
19+
%__MODULE__{bytes: bytes}
20+
end
21+
end
22+
23+
@spec to_bytes(t) :: binary
24+
def to_bytes(%__MODULE__{bytes: bytes}) do
25+
bytes
26+
end
27+
28+
@spec to_string(t) :: String.t()
29+
def to_string(%__MODULE__{bytes: bytes}) do
30+
Base.encode16(bytes, case: :lower)
31+
end
32+
33+
@spec to_hex(t) :: String.t()
34+
def to_hex(%__MODULE__{} = address) do
35+
"0x#{__MODULE__.to_string(address)}"
36+
end
37+
38+
@spec to_checksum(t) :: String.t()
39+
def to_checksum(%__MODULE__{} = address) do
40+
address = address |> __MODULE__.to_string()
41+
address_hash = address |> ExKeccak.hash_256() |> Base.encode16(case: :lower)
42+
43+
keccak_hash_list =
44+
address_hash
45+
|> String.split("", trim: true)
46+
|> Enum.map(fn x -> elem(Integer.parse(x, 16), 0) end)
47+
48+
list_arr =
49+
for n <- 0..(String.length(address) - 1) do
50+
number = Enum.at(keccak_hash_list, n)
51+
52+
cond do
53+
number >= 8 -> String.upcase(String.at(address, n))
54+
true -> String.downcase(String.at(address, n))
55+
end
56+
end
57+
58+
"0x" <> List.to_string(list_arr)
59+
end
60+
61+
@spec is_valid_checksum?(String.t()) :: boolean
62+
def is_valid_checksum?(hex_address) do
63+
address = hex_address |> __MODULE__.from_hex()
64+
__MODULE__.to_checksum(address) == hex_address
65+
end
66+
end
67+

lib/exw3/utils.ex

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
defmodule ExW3.Utils do
2+
alias ExW3.Address
3+
24
@type invalid_hex_string :: :invalid_hex_string
35
@type negative_integer :: :negative_integer
46
@type non_integer :: :non_integer
@@ -86,39 +88,20 @@ defmodule ExW3.Utils do
8688
end
8789
end
8890

91+
@deprecated "Use ExW3.Address.to_checksum/1 instead."
8992
@doc "Returns a checksummed address conforming to EIP-55"
9093
@spec to_checksum_address(String.t()) :: String.t()
9194
def to_checksum_address(address) do
92-
address = address |> String.downcase() |> String.replace(~r/^0x/, "")
93-
hash_bin = ExKeccak.hash_256(address)
94-
95-
hash =
96-
hash_bin
97-
|> Base.encode16(case: :lower)
98-
|> String.replace(~r/^0x/, "")
99-
100-
keccak_hash_list =
101-
hash
102-
|> String.split("", trim: true)
103-
|> Enum.map(fn x -> elem(Integer.parse(x, 16), 0) end)
104-
105-
list_arr =
106-
for n <- 0..(String.length(address) - 1) do
107-
number = Enum.at(keccak_hash_list, n)
108-
109-
cond do
110-
number >= 8 -> String.upcase(String.at(address, n))
111-
true -> String.downcase(String.at(address, n))
112-
end
113-
end
114-
115-
"0x" <> List.to_string(list_arr)
95+
address
96+
|> Address.from_hex()
97+
|> Address.to_checksum()
11698
end
11799

100+
@deprecated "Use ExW3.Address.is_valid_checksum?/1 instead."
118101
@doc "Checks if the address is a valid checksummed address"
119102
@spec is_valid_checksum_address(String.t()) :: boolean
120103
def is_valid_checksum_address(address) do
121-
ExW3.Utils.to_checksum_address(address) == address
104+
Address.is_valid_checksum?(address)
122105
end
123106

124107
@doc "converts Ethereum style bytes to string"
@@ -139,9 +122,12 @@ defmodule ExW3.Utils do
139122
|> :binary.decode_unsigned()
140123
end
141124

125+
@deprecated "Use ExW3.Address.to_hex/1 instead."
142126
@doc "Converts bytes to Ethereum address"
143127
@spec to_address(binary()) :: binary()
144128
def to_address(bytes) do
145-
Enum.join(["0x", bytes |> Base.encode16(case: :lower)], "")
129+
bytes
130+
|> Address.from_bytes()
131+
|> Address.to_hex()
146132
end
147133
end

test/exw3/address_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
defmodule ExW3.AddressTest do
2+
use ExUnit.Case
3+
alias ExW3.Address
4+
5+
@bytes_address <<25, 154, 209, 226, 223, 37, 243, 29, 135, 172, 137, 205, 225, 158, 82, 44, 130, 62, 90, 166, 30>>
6+
@string_address "199ad1e2df25f31d87ac89cde19e522c823e5aa61e"
7+
@hex_address "0x199ad1e2df25f31d87ac89cde19e522c823e5aa61e"
8+
@checksum_address "0x199ad1E2dF25f31D87AC89cdE19E522c823E5AA61e"
9+
10+
test ".from_bytes/1 returns an address struct" do
11+
address = Address.from_bytes(@bytes_address)
12+
assert address.bytes == @bytes_address
13+
end
14+
15+
test ".from_hex/1 returns an address struct with and without the checksum" do
16+
address = Address.from_hex(@hex_address)
17+
assert address.bytes == @bytes_address
18+
19+
from_checksum_address = Address.from_hex(@checksum_address)
20+
assert from_checksum_address.bytes == @bytes_address
21+
end
22+
23+
test ".to_bytes/1 returns the bytes of the address struct" do
24+
address = %Address{bytes: @bytes_address}
25+
assert Address.to_bytes(address) == @bytes_address
26+
end
27+
28+
test ".to_string/1 returns the hex encoded string without a 0x prefix" do
29+
address = %Address{bytes: @bytes_address}
30+
assert Address.to_string(address) == @string_address
31+
end
32+
33+
test ".to_hex/1 returns the hex encoded string with a 0x prefix" do
34+
address = %Address{bytes: @bytes_address}
35+
assert Address.to_hex(address) == @hex_address
36+
end
37+
38+
test ".to_checksum/1 returns the hex encoded string with a 0x prefix conforming to EIP-55" do
39+
address = %Address{bytes: @bytes_address}
40+
assert Address.to_checksum(address) == @checksum_address
41+
end
42+
43+
test ".is_valid_checksum?/1 is true when it conforms to EIP-55" do
44+
assert Address.is_valid_checksum?(@checksum_address) == true
45+
assert Address.is_valid_checksum?(@hex_address) == false
46+
end
47+
end

test/exw3_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ defmodule ExW3Test do
332332

333333
{:ok, same_address} = ExW3.Contract.call(:AddressTester, :get, [formatted_address])
334334

335-
assert ExW3.Utils.to_address(same_address) == Enum.at(context[:accounts], 0)
335+
assert %ExW3.Address{bytes: same_address} |> ExW3.Address.to_hex() == Enum.at(context[:accounts], 0)
336336
end
337337

338338
test "returns proper error messages at contract deployment", context do

0 commit comments

Comments
 (0)