Skip to content

Commit

Permalink
Merge pull request #92 from davesque/fix-dynamic-arrays
Browse files Browse the repository at this point in the history
Update arrays to use head-tail encoding
  • Loading branch information
davesque committed Aug 22, 2018
2 parents d67e89c + 3342b54 commit 082819a
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 18 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ common: &common
when: on_fail
- restore_cache:
keys:
- v2-cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
- v4-cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
- run:
name: install dependencies
command: pip install --user tox
Expand All @@ -33,7 +33,7 @@ common: &common
- ~/.cache/pip
- ~/.local
- ./eggs
key: v3-cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
key: v4-cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}

jobs:
doctest:
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Table of Contents
encoding
decoding
registry
nested_dynamic_arrays
eth_abi
releases

Expand Down
12 changes: 12 additions & 0 deletions docs/nested_dynamic_arrays.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Nested Dynamic Arrays
=====================

The ``eth-abi`` library supports the Solidity ABIv2 encoding format for nested
dynamic arrays. This means that values for data types such as the following
are legal and encodable/decodable: ``int[][]``, ``string[]``, ``string[2]``,
etc.

.. warning::

Though Solidity's ABIv2 has mostly been finalized, the specification is
technically still in development and may change.
12 changes: 12 additions & 0 deletions eth_abi/decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ def split_data_and_padding(self, raw_data):
class BaseArrayDecoder(BaseDecoder):
item_decoder = None

def __init__(self, **kwargs):
super().__init__(**kwargs)

# Use a head-tail decoder to decode dynamic elements
if self.item_decoder.is_dynamic:
self.item_decoder = HeadTailDecoder(
tail_decoder=self.item_decoder,
)

def validate(self):
super().validate()

Expand Down Expand Up @@ -246,13 +255,16 @@ def decode(self, stream):


class DynamicArrayDecoder(BaseArrayDecoder):
# Dynamic arrays are always dynamic, regardless of their elements
is_dynamic = True

@to_tuple
def decode(self, stream):
array_size = decode_uint_256(stream)
stream.push_frame(32)
for _ in range(array_size):
yield self.item_decoder(stream)
stream.pop_frame()


class FixedByteSizeDecoder(SingleDecoder):
Expand Down
18 changes: 13 additions & 5 deletions eth_abi/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,12 +617,20 @@ def validate_value(self, value):
def encode_elements(self, value):
self.validate_value(value)

encoded_elements = b''.join((
self.item_encoder(item)
for item in value
))
item_encoder = self.item_encoder
tail_chunks = tuple(item_encoder(i) for i in value)

return encoded_elements
items_are_dynamic = getattr(item_encoder, 'is_dynamic', False)
if not items_are_dynamic:
return b''.join(tail_chunks)

head_length = 32 * len(value)
tail_offsets = (0,) + tuple(accumulate(map(len, tail_chunks[:-1])))
head_chunks = tuple(
encode_uint_256(head_length + offset)
for offset in tail_offsets
)
return b''.join(head_chunks + tail_chunks)

@parse_type_str(with_arrlist=True)
def from_type_str(cls, abi_type, registry):
Expand Down
2 changes: 1 addition & 1 deletion eth_abi/utils/padding.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cytoolz import (
from eth_utils.toolz import (
curry,
)

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
url='https://github.com/ethereum/eth-abi',
include_package_data=True,
install_requires=[
'eth-utils>=1.0.1,<2.0.0',
'eth-utils>=1.2.0,<2.0.0',
'eth-typing<=2',
'parsimonious==0.8.0',
],
Expand Down
81 changes: 75 additions & 6 deletions tests/common/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,89 @@ def words(*descriptions: str) -> bytes:
),
(
'((int,int[],(int,int)[]),(int,int),int)',
((1, (2, 3), ((4, 5), (6, 7))), (8, 9), 10),
words('80', '8', '9', 'a', '1', '60', 'c0', '2', '2', '3', '2', '4', '5', '6', '7'),
(
(
1,
(2, 3),
((4, 5), (6, 7)),
),
(8, 9),
10,
),
words(
'80', # offset of dynamic tuple
'8', # outer tuple static tuple
'9',
'a', # outer tuple int
'1', # beginning of dynamic tuple (int)
'60', # offset of dynamic tuple list of ints
'c0', # offset of dynamic tuple list of tuples
'2', # size of list of ints
'2',
'3',
'2', # size of list of tuples
'4', # beginning of first tuple
'5',
'6', # beginning of second tuple
'7',
),
words('1', '2', '3', '4', '5', '6', '7', '8', '9', 'a'),
),
(
'(int,int)[][]',
(((1, 2),), ((3, 4), (5, 6)), ((7, 8), (9, 10), (11, 12))),
words('3', '1', '1', '2', '2', '3', '4', '5', '6', '3', '7', '8', '9', 'a', 'b', 'c'),
(
((1, 2),),
((3, 4), (5, 6)),
((7, 8), (9, 10), (11, 12)),
),
words(
'3', # size of outer dynamic list
'60', # offset of first dynamic list
'c0', # offset of second dynamic list
'160', # offset of third dynamic list
'1', # size of first list
'1', # start of first tuple
'2',
'2', # size of second list
'3', # start of second tuple
'4',
'5', # start of third tuple
'6',
'3', # size of third list
'7', # start of fourth tuple
'8',
'9', # start of fifth tuple
'a',
'b', # start of sixth tuple
'c',
),
words('1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c'),
),
(
'((int,int)[][2])',
((((1, 2), (3, 4)), ((5, 6), (7, 8), (9, 10))),),
words('20', '2', '1', '2', '3', '4', '3', '5', '6', '7', '8', '9', 'a'),
(
(
((1, 2), (3, 4)),
((5, 6), (7, 8), (9, 10)),
),
),
words(
'20', # offset of constant size array
'40', # offset of first dynamic list of tuples
'e0', # offset of second dynamic list of tuples
'2', # size of first list
'1', # start of first tuple
'2',
'3', # start of second tuple
'4',
'3', # size of second list
'5', # start of third tuple
'6',
'7', # start of fourth tuple
'8',
'9', # start of fifth tuple
'a',
),
words('1', '2', '3', '4', '5', '6', '7', '8', '9', 'a'),
),
]
Expand Down
6 changes: 3 additions & 3 deletions tests/test_decoding/test_decoder_properties.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import sys

from cytoolz import (
complement,
)
from eth_utils import (
big_endian_to_int,
decode_hex,
int_to_big_endian,
to_normalized_address,
)
from eth_utils.toolz import (
complement,
)
from hypothesis import (
example,
given,
Expand Down

0 comments on commit 082819a

Please sign in to comment.