Skip to content

Commit 5ea2e10

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

File tree

2 files changed

+224
-1
lines changed

2 files changed

+224
-1
lines changed

tests/core/utilities/test_abi.py

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,175 @@
1-
1+
import json
22
import pytest
33

44
from web3._utils.abi import (
55
abi_data_tree,
6+
get_abi_inputs,
67
map_abi_data,
78
)
89
from web3._utils.normalizers import (
910
BASE_RETURN_NORMALIZERS,
1011
)
1112

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

13174
@pytest.mark.parametrize(
14175
'types, data, expected',

web3/_utils/abi.py

Lines changed: 62 additions & 0 deletions
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)