Skip to content

Commit 586bada

Browse files
authored
Implement Packed encoding (#154)
* Fix typo in TypeEncoder test * Implement packed encoding Closes #139
1 parent c8056a8 commit 586bada

File tree

3 files changed

+206
-64
lines changed

3 files changed

+206
-64
lines changed

lib/abi.ex

+27-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,33 @@ defmodule ABI do
5858
end
5959

6060
def encode(%FunctionSelector{} = function_selector, data, data_type) do
61-
TypeEncoder.encode(data, function_selector, data_type)
61+
TypeEncoder.encode(data, function_selector, data_type, :standard)
62+
end
63+
64+
@doc """
65+
Encodes the given data into the given types in packed encoding mode.
66+
67+
Note that packed encoding mode is ambiguous and cannot be decoded (there are no decode_packed functions).
68+
Also, tuples (structs) and nester arrays are not supported.
69+
70+
More info https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode
71+
72+
## Examples
73+
74+
iex> ABI.encode_packed([{:uint, 16}], [0x12])
75+
...> |> Base.encode16(case: :lower)
76+
"0012"
77+
78+
iex> ABI.encode_packed([:string, {:uint, 16}], ["Elixir ABI", 0x12])
79+
...> |> Base.encode16(case: :lower)
80+
"456c69786972204142490012"
81+
82+
iex> ABI.encode_packed([{:int, 16}, {:bytes, 1}, {:uint, 16}, :string], [-1, <<0x42>>, 0x03, "Hello, world!"])
83+
...> |> Base.encode16(case: :lower)
84+
"ffff42000348656c6c6f2c20776f726c6421"
85+
"""
86+
def encode_packed(types, data) when is_list(types) do
87+
TypeEncoder.encode(data, types, :input, :packed)
6288
end
6389

6490
@doc """

lib/abi/type_encoder.ex

+117-62
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,39 @@ defmodule ABI.TypeEncoder do
99

1010
@doc """
1111
Encodes the given data based on the function selector.
12+
13+
## Parameters
14+
- data: The data to encode
15+
- selector_or_types: Either a FunctionSelector struct or a list of types to encode the data with
16+
- data_type: Determines which types to use from a FunctionSelector struct. Can be `:input` or `:output`.
17+
- mode: Encoding mode. Can be `:standard` or `:packed`.
1218
"""
1319

14-
def encode(data, selector_or_types, data_type \\ :input)
20+
def encode(data, selector_or_types, data_type \\ :input, mode \\ :standard)
1521

16-
def encode(data, %FunctionSelector{function: nil, types: types}, :input) do
17-
do_encode(data, types)
22+
def encode(data, %FunctionSelector{function: nil, types: types}, :input, mode) do
23+
do_encode(data, types, mode)
1824
end
1925

20-
def encode(data, %FunctionSelector{types: types} = function_selector, :input) do
21-
encode_method_id(function_selector) <> do_encode(data, types)
26+
def encode(data, %FunctionSelector{types: types} = function_selector, :input, mode) do
27+
encode_method_id(function_selector) <> do_encode(data, types, mode)
2228
end
2329

24-
def encode(data, %FunctionSelector{returns: types}, :output) do
25-
do_encode(data, types)
30+
def encode(data, %FunctionSelector{returns: types}, :output, mode) do
31+
do_encode(data, types, mode)
2632
end
2733

28-
def encode(data, types, _) when is_list(types) do
29-
do_encode(data, types)
34+
def encode(data, types, _, mode) when is_list(types) do
35+
do_encode(data, types, mode)
3036
end
3137

32-
def encode_raw(data, types) when is_list(types) do
33-
do_encode(data, types)
38+
def encode_raw(data, types, mode) when is_list(types) do
39+
do_encode(data, types, mode)
3440
end
3541

36-
defp do_encode(params, types, static_acc \\ [], dynamic_acc \\ [])
42+
defp do_encode(params, types, static_acc \\ [], dynamic_acc \\ [], mode)
3743

38-
defp do_encode([], [], reversed_static_acc, reversed_dynamic_acc) do
44+
defp do_encode([], [], reversed_static_acc, reversed_dynamic_acc, :standard) do
3945
static_acc = Enum.reverse(reversed_static_acc)
4046
dynamic_acc = Enum.reverse(reversed_dynamic_acc)
4147

@@ -61,104 +67,131 @@ defmodule ABI.TypeEncoder do
6167
{complete_static_part, _} =
6268
Enum.reduce(dynamic_indexes, {static_acc, static_part_size}, fn {index, byte_size},
6369
{acc, size_acc} ->
64-
new_static_acc = List.replace_at(acc, index, encode_uint(size_acc, 256))
65-
new_prefix_size = byte_size + size_acc
70+
new_static_acc = List.replace_at(acc, index, encode_uint(size_acc, 256, :standard))
6671

67-
{new_static_acc, new_prefix_size}
72+
{new_static_acc, byte_size + size_acc}
6873
end)
6974

7075
Enum.join(complete_static_part ++ dynamic_acc)
7176
end
7277

78+
defp do_encode([], [], static_acc, dynamic_acc, :packed) do
79+
{values_acc, []} =
80+
Enum.reduce(static_acc, {[], dynamic_acc}, fn
81+
{:dynamic, _}, {values_acc, [value | dynamic_acc]} ->
82+
{[value | values_acc], dynamic_acc}
83+
84+
value, {values_acc, dynamic_acc} ->
85+
{[value | values_acc], dynamic_acc}
86+
end)
87+
88+
Enum.join(values_acc)
89+
end
90+
7391
defp do_encode(
7492
[current_parameter | remaining_parameters],
7593
[current_type | remaining_types],
7694
static_acc,
77-
dynamic_acc
95+
dynamic_acc,
96+
mode
7897
) do
7998
{new_static_acc, new_dynamic_acc} =
80-
do_encode_type(current_type, current_parameter, static_acc, dynamic_acc)
99+
do_encode_type(current_type, current_parameter, static_acc, dynamic_acc, mode)
81100

82-
do_encode(remaining_parameters, remaining_types, new_static_acc, new_dynamic_acc)
101+
do_encode(remaining_parameters, remaining_types, new_static_acc, new_dynamic_acc, mode)
83102
end
84103

85-
defp do_encode_type(:bool, parameter, static_part, dynamic_part) do
104+
defp do_encode_type(:bool, parameter, static_part, dynamic_part, mode) do
86105
value =
87106
case parameter do
88-
true -> encode_uint(1, 8)
89-
false -> encode_uint(0, 8)
107+
true -> encode_uint(1, 8, mode)
108+
false -> encode_uint(0, 8, mode)
90109
_ -> raise "Invalid data for bool: #{inspect(parameter)}"
91110
end
92111

93112
{[value | static_part], dynamic_part}
94113
end
95114

96-
defp do_encode_type({:uint, size}, parameter, static_part, dynamic_part) do
97-
value = encode_uint(parameter, size)
115+
defp do_encode_type({:uint, size}, parameter, static_part, dynamic_part, mode) do
116+
value = encode_uint(parameter, size, mode)
98117

99118
{[value | static_part], dynamic_part}
100119
end
101120

102-
defp do_encode_type({:int, size}, parameter, static_part, dynamic_part) do
103-
value = encode_int(parameter, size)
121+
defp do_encode_type({:int, size}, parameter, static_part, dynamic_part, mode) do
122+
value = encode_int(parameter, size, mode)
104123

105124
{[value | static_part], dynamic_part}
106125
end
107126

108-
defp do_encode_type(:string, parameter, static_part, dynamic_part) do
109-
do_encode_type(:bytes, parameter, static_part, dynamic_part)
127+
defp do_encode_type(:string, parameter, static_part, dynamic_part, mode) do
128+
do_encode_type(:bytes, parameter, static_part, dynamic_part, mode)
110129
end
111130

112-
defp do_encode_type(:bytes, parameter, static_part, dynamic_part) do
131+
defp do_encode_type(:bytes, parameter, static_part, dynamic_part, mode) do
113132
binary_param = maybe_encode_unsigned(parameter)
114-
value = encode_uint(byte_size(binary_param), 256) <> encode_bytes(binary_param)
133+
134+
value =
135+
case mode do
136+
:standard ->
137+
encode_uint(byte_size(binary_param), 256, mode) <> encode_bytes(binary_param, mode)
138+
139+
:packed ->
140+
encode_bytes(binary_param, mode)
141+
end
115142

116143
dynamic_part_byte_size = byte_size(value)
117144

118145
{[{:dynamic, dynamic_part_byte_size} | static_part], [value | dynamic_part]}
119146
end
120147

121-
defp do_encode_type({:bytes, size}, parameter, static_part, dynamic_part)
148+
defp do_encode_type({:bytes, size}, parameter, static_part, dynamic_part, mode)
122149
when is_binary(parameter) and byte_size(parameter) <= size do
123-
value = encode_bytes(parameter)
150+
value = encode_bytes(parameter, mode)
124151

125152
{[value | static_part], dynamic_part}
126153
end
127154

128-
defp do_encode_type({:bytes, size}, data, _, _) when is_binary(data) do
155+
defp do_encode_type({:bytes, size}, data, _, _, _) when is_binary(data) do
129156
raise "size mismatch for bytes#{size}: #{inspect(data)}"
130157
end
131158

132-
defp do_encode_type({:bytes, size}, data, static_part, dynamic_part) when is_integer(data) do
159+
defp do_encode_type({:bytes, size}, data, static_part, dynamic_part, mode)
160+
when is_integer(data) do
133161
binary_param = maybe_encode_unsigned(data)
134162

135-
do_encode_type({:bytes, size}, binary_param, static_part, dynamic_part)
163+
do_encode_type({:bytes, size}, binary_param, static_part, dynamic_part, mode)
136164
end
137165

138-
defp do_encode_type({:bytes, size}, data, _, _) do
166+
defp do_encode_type({:bytes, size}, data, _, _, _) do
139167
raise "wrong datatype for bytes#{size}: #{inspect(data)}"
140168
end
141169

142-
defp do_encode_type({:array, type}, data, static_acc, dynamic_acc) do
170+
defp do_encode_type({:array, type}, data, static_acc, dynamic_acc, mode) do
143171
param_count = Enum.count(data)
144172

145-
encoded_size = encode_uint(param_count, 256)
146-
147173
types = List.duplicate(type, param_count)
148174

149-
result = do_encode(data, types)
175+
result = do_encode(data, types, mode)
150176

151-
dynamic_acc_with_size = [encoded_size | dynamic_acc]
177+
{dynamic_acc_with_size, data_bytes_size} =
178+
case mode do
179+
:standard ->
180+
encoded_size = encode_uint(param_count, 256, mode)
181+
# length is included and also length size is added
182+
{[encoded_size | dynamic_acc], byte_size(result) + 32}
152183

153-
# number of elements count + data size
154-
data_bytes_size = byte_size(result) + 32
184+
:packed ->
185+
# ignoring length of array
186+
{dynamic_acc, byte_size(result)}
187+
end
155188

156189
{[{:dynamic, data_bytes_size} | static_acc], [result | dynamic_acc_with_size]}
157190
end
158191

159-
defp do_encode_type({:array, type, size}, data, static_acc, dynamic_acc) do
192+
defp do_encode_type({:array, type, size}, data, static_acc, dynamic_acc, mode) do
160193
types = List.duplicate(type, size)
161-
result = do_encode(data, types)
194+
result = do_encode(data, types, mode)
162195

163196
if FunctionSelector.is_dynamic?(type) do
164197
data_bytes_size = byte_size(result)
@@ -169,20 +202,30 @@ defmodule ABI.TypeEncoder do
169202
end
170203
end
171204

172-
defp do_encode_type(:address, data, static_acc, dynamic_acc) do
173-
do_encode_type({:uint, 160}, data, static_acc, dynamic_acc)
205+
defp do_encode_type(:address, data, static_acc, dynamic_acc, mode) do
206+
do_encode_type({:uint, 160}, data, static_acc, dynamic_acc, mode)
207+
end
208+
209+
defp do_encode_type({:tuple, _types}, _, _, _, :packed) do
210+
raise RuntimeError, "Structs (tuples) are not supported in packed mode encoding"
174211
end
175212

176-
defp do_encode_type(type = {:tuple, _types}, tuple_parameters, static_acc, dynamic_acc)
213+
defp do_encode_type(
214+
type = {:tuple, _types},
215+
tuple_parameters,
216+
static_acc,
217+
dynamic_acc,
218+
:standard
219+
)
177220
when is_tuple(tuple_parameters) do
178221
list_parameters = Tuple.to_list(tuple_parameters)
179222

180-
do_encode_type(type, list_parameters, static_acc, dynamic_acc)
223+
do_encode_type(type, list_parameters, static_acc, dynamic_acc, :standard)
181224
end
182225

183-
defp do_encode_type(type = {:tuple, types}, list_parameters, static_acc, dynamic_acc)
226+
defp do_encode_type(type = {:tuple, types}, list_parameters, static_acc, dynamic_acc, :standard)
184227
when is_list(list_parameters) do
185-
result = do_encode(list_parameters, types)
228+
result = do_encode(list_parameters, types, :standard)
186229

187230
if FunctionSelector.is_dynamic?(type) do
188231
data_bytes_size = byte_size(result)
@@ -193,8 +236,8 @@ defmodule ABI.TypeEncoder do
193236
end
194237
end
195238

196-
defp encode_bytes(bytes) do
197-
pad(bytes, byte_size(bytes), :right)
239+
defp encode_bytes(bytes, mode) do
240+
pad(bytes, byte_size(bytes), :right, mode)
198241
end
199242

200243
@spec encode_method_id(FunctionSelector.t()) :: binary()
@@ -216,7 +259,7 @@ defmodule ABI.TypeEncoder do
216259

217260
# Note, we'll accept a binary or an integer here, so long as the
218261
# binary is not longer than our allowed data size
219-
defp encode_uint(data, size_in_bits) when rem(size_in_bits, 8) == 0 do
262+
defp encode_uint(data, size_in_bits, mode) when rem(size_in_bits, 8) == 0 do
220263
size_in_bytes = (size_in_bits / 8) |> round
221264
bin = maybe_encode_unsigned(data)
222265

@@ -226,22 +269,22 @@ defmodule ABI.TypeEncoder do
226269
"Data overflow encoding uint, data `#{data}` cannot fit in #{size_in_bytes * 8} bits"
227270
)
228271

229-
bin |> pad(size_in_bytes, :left)
272+
bin |> pad(size_in_bytes, :left, mode)
230273
end
231274

232-
defp encode_int(data, size_in_bits) when rem(size_in_bits, 8) == 0 do
275+
defp encode_int(data, size_in_bits, mode) when rem(size_in_bits, 8) == 0 do
233276
if signed_overflow?(data, size_in_bits) do
234277
raise("Data overflow encoding int, data `#{data}` cannot fit in #{size_in_bits} bits")
235278
end
236279

237-
encode_int(data)
280+
case mode do
281+
:standard -> <<data::signed-256>>
282+
:packed -> <<data::signed-size(size_in_bits)>>
283+
end
238284
end
239285

240-
# encoding with integer-signed-256 we already get the right padding
241-
defp encode_int(data), do: <<data::signed-256>>
242-
243286
defp signed_overflow?(n, max_bits) do
244-
n < :math.pow(2, max_bits - 1) * -1 + 1 || n > :math.pow(2, max_bits - 1) - 1
287+
n < 2 ** (max_bits - 1) * -1 + 1 || n > 2 ** (max_bits - 1) - 1
245288
end
246289

247290
def mod(x, n) do
@@ -252,7 +295,19 @@ defmodule ABI.TypeEncoder do
252295
else: remainder
253296
end
254297

255-
defp pad(bin, size_in_bytes, direction) do
298+
defp pad(bin, size_in_bytes, _direction, :packed) when byte_size(bin) == size_in_bytes, do: bin
299+
300+
defp pad(bin, size_in_bytes, direction, :packed) when byte_size(bin) < size_in_bytes do
301+
padding_size_bits = (size_in_bytes - byte_size(bin)) * 8
302+
padding = <<0::size(padding_size_bits)>>
303+
304+
case direction do
305+
:left -> padding <> bin
306+
:right -> bin <> padding
307+
end
308+
end
309+
310+
defp pad(bin, size_in_bytes, direction, :standard) do
256311
total_size = size_in_bytes + mod(32 - size_in_bytes, 32)
257312
padding_size_bits = (total_size - byte_size(bin)) * 8
258313
padding = <<0::size(padding_size_bits)>>

0 commit comments

Comments
 (0)