Skip to content

Commit d734c8a

Browse files
committed
Add get_abi_inputs utility function
1 parent 8bd60d3 commit d734c8a

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

tests/core/utilities/test_abi.py

+162
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,176 @@
1+
import json
12

23
import pytest
34

45
from web3._utils.abi import (
56
abi_data_tree,
7+
get_abi_inputs,
68
map_abi_data,
79
)
810
from web3._utils.normalizers import (
911
BASE_RETURN_NORMALIZERS,
1012
)
1113

14+
TEST_FUNCTION_ABI_JSON = """
15+
{
16+
"constant": false,
17+
"inputs": [
18+
{
19+
"components": [
20+
{
21+
"name": "a",
22+
"type": "uint256"
23+
},
24+
{
25+
"name": "b",
26+
"type": "uint256[]"
27+
},
28+
{
29+
"components": [
30+
{
31+
"name": "x",
32+
"type": "uint256"
33+
},
34+
{
35+
"name": "y",
36+
"type": "uint256"
37+
}
38+
],
39+
"name": "c",
40+
"type": "tuple[]"
41+
}
42+
],
43+
"name": "s",
44+
"type": "tuple"
45+
},
46+
{
47+
"components": [
48+
{
49+
"name": "x",
50+
"type": "uint256"
51+
},
52+
{
53+
"name": "y",
54+
"type": "uint256"
55+
}
56+
],
57+
"name": "t",
58+
"type": "tuple"
59+
},
60+
{
61+
"name": "a",
62+
"type": "uint256"
63+
}
64+
],
65+
"name": "f",
66+
"outputs": [],
67+
"payable": false,
68+
"stateMutability": "nonpayable",
69+
"type": "function"
70+
}
71+
"""
72+
TEST_FUNCTION_ABI = json.loads(TEST_FUNCTION_ABI_JSON)
73+
74+
75+
GET_ABI_INPUTS_OUTPUT = (
76+
(
77+
'(uint256,uint256[],(uint256,uint256)[])', # Type of s
78+
'(uint256,uint256)', # Type of t
79+
'uint256', # Type of a
80+
),
81+
(
82+
(1, [2, 3, 4], [(5, 6), (7, 8), (9, 10)]), # Value for s
83+
(11, 12), # Value for t
84+
13, # Value for a
85+
),
86+
)
87+
88+
GET_ABI_INPUTS_TESTS = (
89+
(
90+
(
91+
TEST_FUNCTION_ABI,
92+
{
93+
's': {
94+
'a': 1,
95+
'b': [2, 3, 4],
96+
'c': [{'x': 5, 'y': 6}, {'x': 7, 'y': 8}, {'x': 9, 'y': 10}],
97+
},
98+
't': {'x': 11, 'y': 12},
99+
'a': 13,
100+
},
101+
),
102+
GET_ABI_INPUTS_OUTPUT,
103+
),
104+
(
105+
(
106+
TEST_FUNCTION_ABI,
107+
{
108+
's': {
109+
'a': 1,
110+
'b': [2, 3, 4],
111+
'c': [(5, 6), (7, 8), {'x': 9, 'y': 10}],
112+
},
113+
't': {'x': 11, 'y': 12},
114+
'a': 13,
115+
},
116+
),
117+
GET_ABI_INPUTS_OUTPUT,
118+
),
119+
(
120+
(
121+
TEST_FUNCTION_ABI,
122+
{
123+
's': {
124+
'a': 1,
125+
'b': [2, 3, 4],
126+
'c': [(5, 6), (7, 8), (9, 10)],
127+
},
128+
't': (11, 12),
129+
'a': 13,
130+
},
131+
),
132+
GET_ABI_INPUTS_OUTPUT,
133+
),
134+
(
135+
(
136+
TEST_FUNCTION_ABI,
137+
{
138+
's': (
139+
1,
140+
[2, 3, 4],
141+
[(5, 6), (7, 8), (9, 10)],
142+
),
143+
't': (11, 12),
144+
'a': 13,
145+
},
146+
),
147+
GET_ABI_INPUTS_OUTPUT,
148+
),
149+
(
150+
(
151+
TEST_FUNCTION_ABI,
152+
(
153+
(
154+
1,
155+
[2, 3, 4],
156+
[(5, 6), (7, 8), (9, 10)],
157+
),
158+
(11, 12),
159+
13,
160+
),
161+
),
162+
GET_ABI_INPUTS_OUTPUT,
163+
),
164+
)
165+
166+
167+
@pytest.mark.parametrize(
168+
'input, expected',
169+
GET_ABI_INPUTS_TESTS,
170+
)
171+
def test_get_abi_inputs(input, expected):
172+
assert get_abi_inputs(*input) == expected
173+
12174

13175
@pytest.mark.parametrize(
14176
'types, data, expected',

web3/_utils/abi.py

+62
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import binascii
22
from collections import (
3+
abc,
34
namedtuple,
45
)
6+
import copy
57
import itertools
68
import re
79

@@ -345,6 +347,66 @@ def merge_args_and_kwargs(function_abi, args, kwargs):
345347
return tuple()
346348

347349

350+
TUPLE_PREFIX = 'tuple'
351+
TUPLE_PREFIX_LEN = len(TUPLE_PREFIX)
352+
353+
354+
def _convert_abi_input(comp, arg):
355+
"""
356+
Converts an argument ``arg`` corresponding to an ABI component ``comp``
357+
into a plain value (for non-tuple components) or a properly converted and
358+
ordered sequence (for tuple list components or tuple components).
359+
"""
360+
abi_type_str = comp['type']
361+
362+
if not abi_type_str.startswith(TUPLE_PREFIX):
363+
# Component is non-tuple. Just return value.
364+
return arg
365+
366+
# Component is tuple (possibly tuple list)...
367+
368+
array_dims = abi_type_str[TUPLE_PREFIX_LEN:]
369+
if array_dims:
370+
# Component is tuple list (i.e. it has array dimensions). Construct
371+
# new ABI component without array dimensions and recurse with it for
372+
# each element in arg.
373+
new_comp = copy.copy(comp)
374+
new_comp['type'] = TUPLE_PREFIX
375+
376+
# Assume `arg` is iterable
377+
return type(arg)(_convert_abi_input(new_comp, a) for a in arg)
378+
379+
# Component is non-list tuple...
380+
381+
sub_comps = comp['components']
382+
383+
if isinstance(arg, abc.Mapping):
384+
# Arg is mapping. Convert to properly ordered sequence.
385+
arg = tuple(arg[c['name']] for c in sub_comps)
386+
387+
return tuple(_convert_abi_input(c, a) for c, a in zip(sub_comps, arg))
388+
389+
390+
def get_abi_inputs(abi, args):
391+
"""
392+
Takes a function ABI (``abi``) and a sequence or mapping of args
393+
(``args``). Returns a list of canonical type names for the function's
394+
inputs and a list of arguments in corresponding order. The args contained
395+
in ``args`` may contain nested mappings or sequences corresponding to
396+
tuple-encoded values in ``abi``.
397+
"""
398+
inputs = abi['inputs']
399+
400+
if isinstance(args, abc.Mapping):
401+
# `args` is mapping, convert to properly ordered sequence
402+
args = tuple(args[i['name']] for i in inputs)
403+
404+
return (
405+
tuple(collapse_if_tuple(i) for i in inputs),
406+
tuple(_convert_abi_input(i, a) for i, a in zip(inputs, args))
407+
)
408+
409+
348410
def get_constructor_abi(contract_abi):
349411
candidates = [
350412
abi for abi in contract_abi if abi['type'] == 'constructor'

0 commit comments

Comments
 (0)