Skip to content

Commit 602db5f

Browse files
committed
Check bytes length with strict registry
1 parent e9284cd commit 602db5f

File tree

7 files changed

+114
-57
lines changed

7 files changed

+114
-57
lines changed

tests/core/contracts/test_contract_call_interface.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,12 @@ def test_set_byte_array(arrays_contract, call, transact, args, expected):
281281
assert result == expected
282282

283283

284-
@pytest.mark.parametrize('args,expected', [([b''], [b'\x00']), (['0x'], [b'\x00'])])
284+
@pytest.mark.parametrize(
285+
'args,expected', [
286+
([b'1'], [b'1']),
287+
(['0xDe'], [b'\xDe'])
288+
]
289+
)
285290
def test_set_strict_byte_array(strict_arrays_contract, call, transact, args, expected):
286291
transact(
287292
contract=strict_arrays_contract,

tests/core/contracts/test_contract_method_to_argument_matching.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,20 +152,27 @@ def test_error_when_duplicate_match(web3):
152152
Contract._find_matching_fn_abi('a', [100])
153153

154154

155+
@pytest.mark.parametrize('arguments', (['0xf00b47'], [b''], [''], ['00' * 16]))
156+
def test_strict_errors_if_type_is_wrong(web3_strict_types, arguments):
157+
Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS)
158+
159+
with pytest.raises(ValidationError):
160+
Contract._find_matching_fn_abi('a', arguments)
161+
162+
155163
@pytest.mark.parametrize(
156-
'arguments,expected_types,expected_error',
164+
'arguments,expected_types',
157165
(
158-
# TODO (['0xf00b47'], ['bytes12'], TypeError),
159-
([''], ['bytes'], ValidationError),
166+
([], []),
167+
([1234567890], ['uint256']),
168+
([-1], ['int8']),
169+
([[(-1, True), (2, False)]], ['(int256,bool)[]']),
160170
)
161171
)
162-
def test_errors_if_type_is_wrong(
163-
web3_strict_types,
164-
arguments,
165-
expected_types,
166-
expected_error):
167-
172+
def test_strict_finds_function_with_matching_args(web3_strict_types, arguments, expected_types):
168173
Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS)
169174

170-
with pytest.raises(expected_error):
171-
Contract._find_matching_fn_abi('a', arguments)
175+
abi = Contract._find_matching_fn_abi('a', arguments)
176+
assert abi['name'] == 'a'
177+
assert len(abi['inputs']) == len(expected_types)
178+
assert set(get_abi_input_types(abi)) == set(expected_types)

tests/core/utilities/test_abi_is_encodable.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ def test_is_encodable(value, _type, expected):
8484
(
8585
# Special bytes<M> behavior
8686
('12', 'bytes2', False), # no hex strings without leading 0x
87-
('0x12', 'bytes2', True), # with 0x OK
87+
('0x12', 'bytes1', True), # with 0x OK
8888
('0123', 'bytes2', False), # needs a 0x
89-
# TODO (b'\x12', 'bytes2', False), # no undersize bytes value
89+
(b'\x12', 'bytes2', False), # no undersize bytes value
9090
('0123', 'bytes1', False), # no oversize hex strings
9191
('1', 'bytes2', False), # no odd length
9292
('0x1', 'bytes2', False), # no odd length
9393
9494
# Special bytes behavior
95-
('12', 'bytes', False),
95+
('12', 'bytes', False), # has to have 0x if string
9696
('0x12', 'bytes', True),
9797
('1', 'bytes', False),
9898
('0x1', 'bytes', False),

tests/core/utilities/test_validation.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,3 @@ def test_validate_abi_value(abi_type, value, expected):
147147
return
148148

149149
validate_abi_value(abi_type, value)
150-
151-
152-
@pytest.mark.parametrize(
153-
'abi_type,value,expected',
154-
(
155-
# ('bytes3', b'T\x02', TypeError), TODO - has to exactly match length
156-
('bytes2', b'T\x02', None),
157-
)
158-
)
159-
def test_validate_abi_value_strict(abi_type, value, expected):
160-
161-
if isinstance(expected, type) and issubclass(expected, Exception):
162-
with pytest.raises(expected):
163-
validate_abi_value(abi_type, value)
164-
return
165-
166-
validate_abi_value(abi_type, value)

web3/_utils/abi.py

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
from eth_abi import (
1818
encoding,
1919
)
20+
from eth_abi.base import (
21+
parse_type_str,
22+
)
23+
from eth_abi.exceptions import (
24+
ValueOutOfBounds,
25+
)
2026
from eth_abi.grammar import (
2127
ABIType,
2228
TupleType,
@@ -129,19 +135,6 @@ def filter_by_argument_name(argument_names, contract_abi):
129135
]
130136

131137

132-
def length_of_bytes_type(abi_type):
133-
if not is_bytes_type(abi_type):
134-
raise ValueError(
135-
f"Cannot parse length of nonbytes abi-type: {abi_type}"
136-
)
137-
138-
byte_length = re.search('(\d{1,2})', abi_type)
139-
if not byte_length:
140-
return None
141-
else:
142-
return int(byte_length.group(0))
143-
144-
145138
class AddressEncoder(encoding.AddressEncoder):
146139
@classmethod
147140
def validate_value(cls, value):
@@ -173,7 +166,6 @@ def validate_value(self, value):
173166
class StrictAcceptsHexStrMixin:
174167
def validate_value(self, value):
175168
if is_text(value) and value[:2] != '0x':
176-
print('in strict accepts hexstr mixin')
177169
self.invalidate_value(
178170
value,
179171
msg='hex string must be prefixed with 0x'
@@ -194,15 +186,11 @@ class BytesEncoder(AcceptsHexStrMixin, encoding.BytesEncoder):
194186
pass
195187

196188

197-
class StrictBytesEncoder(StrictAcceptsHexStrMixin, encoding.PackedBytesEncoder):
198-
pass
199-
200-
201189
class ByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder):
202190
pass
203191

204192

205-
class StrictByteStringEncoder(StrictAcceptsHexStrMixin, encoding.PackedByteStringEncoder):
193+
class StrictByteStringEncoder(StrictAcceptsHexStrMixin, encoding.ByteStringEncoder):
206194
pass
207195

208196

@@ -221,6 +209,82 @@ def validate_value(cls, value):
221209
super().validate_value(value)
222210

223211

212+
class ExactLengthBytesEncoder(encoding.BaseEncoder):
213+
is_big_endian = False
214+
value_bit_size = None
215+
data_byte_size = None
216+
encode_fn = None
217+
218+
def validate(self):
219+
super().validate()
220+
221+
if self.value_bit_size is None:
222+
raise ValueError("`value_bit_size` may not be none")
223+
if self.data_byte_size is None:
224+
raise ValueError("`data_byte_size` may not be none")
225+
if self.encode_fn is None:
226+
raise ValueError("`encode_fn` may not be none")
227+
if self.is_big_endian is None:
228+
raise ValueError("`is_big_endian` may not be none")
229+
230+
if self.value_bit_size % 8 != 0:
231+
raise ValueError(
232+
"Invalid value bit size: {0}. Must be a multiple of 8".format(
233+
self.value_bit_size,
234+
)
235+
)
236+
237+
if self.value_bit_size > self.data_byte_size * 8:
238+
raise ValueError("Value byte size exceeds data size")
239+
240+
def encode(self, value):
241+
self.validate_value(value)
242+
return self.encode_fn(value)
243+
244+
def validate_value(self, value):
245+
if not is_bytes(value) and not is_text(value):
246+
self.invalidate_value(value)
247+
248+
if is_text(value) and value[:2] != '0x':
249+
self.invalidate_value(
250+
value,
251+
msg='hex string must be prefixed with 0x'
252+
)
253+
elif is_text(value):
254+
try:
255+
value = decode_hex(value)
256+
except binascii.Error:
257+
self.invalidate_value(
258+
value,
259+
msg='invalid hex string',
260+
)
261+
262+
byte_size = self.value_bit_size // 8
263+
if len(value) > byte_size:
264+
self.invalidate_value(
265+
value,
266+
exc=ValueOutOfBounds,
267+
msg="exceeds total byte size for bytes{} encoding".format(byte_size),
268+
)
269+
elif len(value) < byte_size:
270+
self.invalidate_value(
271+
value,
272+
exc=ValueOutOfBounds,
273+
msg="less than total byte size for bytes{} encoding".format(byte_size),
274+
)
275+
276+
@staticmethod
277+
def encode_fn(value):
278+
return value
279+
280+
@parse_type_str('bytes')
281+
def from_type_str(cls, abi_type, registry):
282+
return cls(
283+
value_bit_size=abi_type.sub * 8,
284+
data_byte_size=abi_type.sub,
285+
)
286+
287+
224288
def filter_by_encodability(w3, args, kwargs, contract_abi):
225289
return [
226290
function_abi
@@ -245,7 +309,7 @@ def check_if_arguments_can_be_encoded(function_abi, w3, args, kwargs):
245309
return False
246310

247311
return all(
248-
w3.codec.is_encodable(_type, arg)
312+
w3.is_encodable(_type, arg)
249313
for _type, arg in zip(types, aligned_args)
250314
)
251315

web3/_utils/filters.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from eth_abi import (
22
decode_abi,
3-
is_encodable,
43
)
54
from eth_abi.grammar import (
65
parse as parse_type_string,
@@ -218,7 +217,7 @@ def match_fn(match_values_and_abi, data):
218217
continue
219218
normalized_data = normalize_data_values(abi_type, data_value)
220219
for value in match_values:
221-
if not is_encodable(abi_type, value):
220+
if not w3.is_encodable(abi_type, value):
222221
raise ValueError(
223222
"Value {0} is of the wrong abi type. "
224223
"Expected {1} typed value.".format(value, abi_type))

web3/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
AddressEncoder,
3030
BytesEncoder,
3131
ByteStringEncoder,
32-
StrictBytesEncoder,
32+
ExactLengthBytesEncoder,
3333
StrictByteStringEncoder,
3434
TextStringEncoder,
3535
)
@@ -161,7 +161,6 @@ def __init__(self, provider=None, middlewares=None, modules=None, ens=empty):
161161
self.ens = ens
162162

163163
def is_encodable(self, _type, value):
164-
# TODO - there is probably a better place to put this
165164
return self.codec.is_encodable(_type, value)
166165

167166
def build_default_registry(self):
@@ -315,7 +314,7 @@ def build_strict_registry(self):
315314
)
316315
registry.register(
317316
BaseEquals('bytes', with_sub=True),
318-
StrictBytesEncoder, decoding.BytesDecoder,
317+
ExactLengthBytesEncoder, decoding.BytesDecoder,
319318
label='bytes<M>',
320319
)
321320
registry.register(

0 commit comments

Comments
 (0)