mirror of
https://github.com/FlipsideCrypto/web3.py.git
synced 2026-02-06 10:56:47 +00:00
make it work
This commit is contained in:
parent
c9e83cd828
commit
e90aed0e43
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from web3.contract import (
|
||||
from web3.utils.abi import (
|
||||
merge_args_and_kwargs,
|
||||
)
|
||||
|
||||
@ -19,6 +19,15 @@ FUNCTION_ABI = {
|
||||
}
|
||||
|
||||
|
||||
NO_INPUTS_FUNCTION_ABI = {
|
||||
"constant": False
|
||||
,"inputs": [],
|
||||
"name": "testFn",
|
||||
"outputs": [],
|
||||
"type":"function",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'args,kwargs,expected_args',
|
||||
(
|
||||
@ -32,3 +41,8 @@ FUNCTION_ABI = {
|
||||
def test_merging_of_args_and_kwargs(args, kwargs, expected_args):
|
||||
actual_args = merge_args_and_kwargs(FUNCTION_ABI, args, kwargs)
|
||||
assert actual_args == expected_args
|
||||
|
||||
|
||||
def test_merging_of_args_and_kwargs_with_no_inputs():
|
||||
actual = merge_args_and_kwargs(NO_INPUTS_FUNCTION_ABI, tuple(), {})
|
||||
assert actual == tuple()
|
||||
|
||||
@ -47,8 +47,16 @@ def test_call_with_one_argument(math_contract):
|
||||
assert result == 21
|
||||
|
||||
|
||||
def test_call_with_multiple_arguments(math_contract):
|
||||
result = math_contract.call().add(9, 7)
|
||||
@pytest.mark.parametrize(
|
||||
'call_args,call_kwargs',
|
||||
(
|
||||
((9, 7), {}),
|
||||
((9,), {'b': 7}),
|
||||
(tuple(), {'a': 9, 'b': 7}),
|
||||
),
|
||||
)
|
||||
def test_call_with_multiple_arguments(math_contract, call_args, call_kwargs):
|
||||
result = math_contract.call().add(*call_args, **call_kwargs)
|
||||
assert result == 16
|
||||
|
||||
|
||||
|
||||
@ -41,6 +41,13 @@ def test_transacting_with_contract_no_arguments(web3_tester, math_contract):
|
||||
assert final_value - initial_value == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'transact_args,transact_kwargs',
|
||||
(
|
||||
((5,), {}),
|
||||
(tuple(), {'amt': 5}),
|
||||
),
|
||||
)
|
||||
def test_transacting_with_contract_with_arguments(web3_tester, math_contract):
|
||||
initial_value = math_contract.call().counter()
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"""Interaction with smart contracts over Web3 connector.
|
||||
|
||||
"""
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from eth_abi import (
|
||||
@ -35,6 +34,7 @@ from web3.utils.abi import (
|
||||
get_constructor_abi,
|
||||
check_if_arguments_can_be_encoded,
|
||||
function_abi_to_4byte_selector,
|
||||
merge_args_and_kwargs,
|
||||
)
|
||||
from web3.utils.decorators import (
|
||||
combomethod,
|
||||
@ -274,16 +274,17 @@ class Contract(object):
|
||||
# ABI Helpers
|
||||
#
|
||||
@classmethod
|
||||
def find_matching_fn_abi(cls, fn_name=None, arguments=None):
|
||||
def find_matching_fn_abi(cls, fn_name=None, args=None, kwargs=None):
|
||||
filters = []
|
||||
|
||||
if fn_name:
|
||||
filters.append(functools.partial(filter_by_name, fn_name))
|
||||
|
||||
if arguments is not None:
|
||||
if args is not None and kwargs is not None:
|
||||
num_arguments = len(args) + len(kwargs)
|
||||
filters.extend([
|
||||
functools.partial(filter_by_argument_count, arguments),
|
||||
functools.partial(filter_by_encodability, arguments),
|
||||
functools.partial(filter_by_argument_count, num_arguments),
|
||||
functools.partial(filter_by_encodability, args, kwargs),
|
||||
])
|
||||
|
||||
function_candidates = filter_by_type('function', cls.abi)
|
||||
@ -370,8 +371,9 @@ class Contract(object):
|
||||
)
|
||||
|
||||
is_encodable = check_if_arguments_can_be_encoded(
|
||||
get_abi_input_types(constructor),
|
||||
constructor,
|
||||
arguments,
|
||||
{},
|
||||
)
|
||||
if not is_encodable:
|
||||
raise ValueError("Unable to encode provided arguments.")
|
||||
@ -620,10 +622,11 @@ class Contract(object):
|
||||
|
||||
|
||||
@coerce_return_to_text
|
||||
def call_contract_function(contract=None,
|
||||
function_name=None,
|
||||
transaction=None,
|
||||
*arguments):
|
||||
def call_contract_function(contract,
|
||||
function_name,
|
||||
transaction,
|
||||
*args,
|
||||
**kwargs):
|
||||
"""Calls a contract constant or function.
|
||||
|
||||
The function must not have state changing effects.
|
||||
@ -643,12 +646,11 @@ def call_contract_function(contract=None,
|
||||
else:
|
||||
call_transaction = dict(**transaction)
|
||||
|
||||
if not arguments:
|
||||
arguments = []
|
||||
|
||||
function_abi = contract.find_matching_fn_abi(function_name, arguments)
|
||||
function_abi = contract.find_matching_fn_abi(function_name, args, kwargs)
|
||||
function_selector = function_abi_to_4byte_selector(function_abi)
|
||||
|
||||
arguments = merge_args_and_kwargs(function_abi, args, kwargs)
|
||||
|
||||
call_transaction['data'] = contract.encodeABI(
|
||||
function_name,
|
||||
arguments,
|
||||
@ -674,7 +676,8 @@ def call_contract_function(contract=None,
|
||||
def transact_with_contract_function(contract=None,
|
||||
function_name=None,
|
||||
transaction=None,
|
||||
*arguments):
|
||||
*args,
|
||||
**kwargs):
|
||||
"""Transacts with a contract.
|
||||
|
||||
Sends in a transaction that interacts with the contract.
|
||||
@ -737,12 +740,11 @@ def transact_with_contract_function(contract=None,
|
||||
else:
|
||||
transact_transaction = dict(**transaction)
|
||||
|
||||
if not arguments:
|
||||
arguments = []
|
||||
|
||||
function_abi = contract.find_matching_fn_abi(function_name, arguments)
|
||||
function_abi = contract.find_matching_fn_abi(function_name, args, kwargs)
|
||||
function_selector = function_abi_to_4byte_selector(function_abi)
|
||||
|
||||
arguments = merge_args_and_kwargs(function_abi, args, kwargs)
|
||||
|
||||
transact_transaction['data'] = contract.encodeABI(
|
||||
function_name,
|
||||
arguments,
|
||||
@ -759,7 +761,8 @@ def transact_with_contract_function(contract=None,
|
||||
def estimate_gas_for_function(contract=None,
|
||||
function_name=None,
|
||||
transaction=None,
|
||||
*arguments):
|
||||
*args,
|
||||
**kwargs):
|
||||
"""Estimates gas cost a function call would take.
|
||||
|
||||
Don't call this directly, instead use :meth:`Contract.estimateGas`
|
||||
@ -770,12 +773,11 @@ def estimate_gas_for_function(contract=None,
|
||||
else:
|
||||
estimate_transaction = dict(**transaction)
|
||||
|
||||
if not arguments:
|
||||
arguments = []
|
||||
|
||||
function_abi = contract.find_matching_fn_abi(function_name, arguments)
|
||||
function_abi = contract.find_matching_fn_abi(function_name, args, kwargs)
|
||||
function_selector = function_abi_to_4byte_selector(function_abi)
|
||||
|
||||
arguments = merge_args_and_kwargs(function_abi, args, kwargs)
|
||||
|
||||
estimate_transaction['data'] = contract.encodeABI(
|
||||
function_name,
|
||||
arguments,
|
||||
@ -843,44 +845,3 @@ def construct_contract_class(web3, abi, code=None,
|
||||
'source': source,
|
||||
}
|
||||
return type('Contract', (Contract,), _dict)
|
||||
|
||||
|
||||
def merge_args_and_kwargs(function_abi, args, kwargs):
|
||||
if len(args) + len(kwargs) != len(function_abi['inputs']):
|
||||
raise TypeError(
|
||||
"Incorrect argument count. Expected '{0}'. Got '{1}'".format(
|
||||
len(function_abi['input']),
|
||||
len(args) + len(kwargs),
|
||||
)
|
||||
)
|
||||
args_as_kwargs = {
|
||||
arg_abi['name']: arg
|
||||
for arg_abi, arg in zip(function_abi['inputs'], args)
|
||||
}
|
||||
duplicate_keys = set(args_as_kwargs).intersection(kwargs.keys())
|
||||
if duplicate_keys:
|
||||
raise TypeError(
|
||||
"{fn_name}() got multiple values for argument(s) '{dups}'".format(
|
||||
fn_name=function_abi['name'],
|
||||
dups=', '.join(duplicate_keys),
|
||||
)
|
||||
)
|
||||
|
||||
sorted_arg_names = [arg_abi['name'] for arg_abi in function_abi['inputs']]
|
||||
|
||||
unknown_kwargs = {key for key in kwargs.keys() if key not in sorted_arg_names}
|
||||
if unknown_kwargs:
|
||||
raise TypeError(
|
||||
"{fn_name}() got unexpected keyword argument(s) '{dups}'".format(
|
||||
fn_name=function_abi['name'],
|
||||
dups=', '.join(unknown_kwargs),
|
||||
)
|
||||
)
|
||||
|
||||
sorted_args = list(zip(
|
||||
*sorted(
|
||||
itertools.chain(kwargs.items(), args_as_kwargs.items()),
|
||||
key=lambda kv: sorted_arg_names.index(kv[0])
|
||||
)
|
||||
))
|
||||
return sorted_args[1]
|
||||
|
||||
@ -53,12 +53,12 @@ def exclude_indexed_event_inputs(event_abi):
|
||||
return [arg for arg in event_abi['inputs'] if arg['indexed'] is False]
|
||||
|
||||
|
||||
def filter_by_argument_count(arguments, contract_abi):
|
||||
def filter_by_argument_count(num_arguments, contract_abi):
|
||||
return [
|
||||
abi
|
||||
for abi
|
||||
in contract_abi
|
||||
if len(abi['inputs']) == len(arguments)
|
||||
if len(abi['inputs']) == num_arguments
|
||||
]
|
||||
|
||||
|
||||
@ -122,25 +122,77 @@ def is_encodable(_type, value):
|
||||
raise ValueError("Unsupported type")
|
||||
|
||||
|
||||
def filter_by_encodability(arguments, contract_abi):
|
||||
def filter_by_encodability(args, kwargs, contract_abi):
|
||||
return [
|
||||
abi
|
||||
for abi
|
||||
function_abi
|
||||
for function_abi
|
||||
in contract_abi
|
||||
if check_if_arguments_can_be_encoded(get_abi_input_types(abi), arguments)
|
||||
if check_if_arguments_can_be_encoded(function_abi, args, kwargs)
|
||||
]
|
||||
|
||||
|
||||
@coerce_args_to_bytes
|
||||
def check_if_arguments_can_be_encoded(types, arguments):
|
||||
if len(types) != len(arguments):
|
||||
raise ValueError("Length mismatch between types and arguments")
|
||||
def check_if_arguments_can_be_encoded(function_abi, args, kwargs):
|
||||
try:
|
||||
arguments = merge_args_and_kwargs(function_abi, args, kwargs)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
if len(function_abi['inputs']) != len(arguments):
|
||||
return False
|
||||
|
||||
types = get_abi_input_types(function_abi)
|
||||
|
||||
return all(
|
||||
is_encodable(_type, arg)
|
||||
for _type, arg in zip(types, arguments)
|
||||
)
|
||||
|
||||
|
||||
def merge_args_and_kwargs(function_abi, args, kwargs):
|
||||
if len(args) + len(kwargs) != len(function_abi['inputs']):
|
||||
raise TypeError(
|
||||
"Incorrect argument count. Expected '{0}'. Got '{1}'".format(
|
||||
len(function_abi['input']),
|
||||
len(args) + len(kwargs),
|
||||
)
|
||||
)
|
||||
args_as_kwargs = {
|
||||
arg_abi['name']: arg
|
||||
for arg_abi, arg in zip(function_abi['inputs'], args)
|
||||
}
|
||||
duplicate_keys = set(args_as_kwargs).intersection(kwargs.keys())
|
||||
if duplicate_keys:
|
||||
raise TypeError(
|
||||
"{fn_name}() got multiple values for argument(s) '{dups}'".format(
|
||||
fn_name=function_abi['name'],
|
||||
dups=', '.join(duplicate_keys),
|
||||
)
|
||||
)
|
||||
|
||||
sorted_arg_names = [arg_abi['name'] for arg_abi in function_abi['inputs']]
|
||||
|
||||
unknown_kwargs = {key for key in kwargs.keys() if key not in sorted_arg_names}
|
||||
if unknown_kwargs:
|
||||
raise TypeError(
|
||||
"{fn_name}() got unexpected keyword argument(s) '{dups}'".format(
|
||||
fn_name=function_abi['name'],
|
||||
dups=', '.join(unknown_kwargs),
|
||||
)
|
||||
)
|
||||
|
||||
sorted_args = list(zip(
|
||||
*sorted(
|
||||
itertools.chain(kwargs.items(), args_as_kwargs.items()),
|
||||
key=lambda kv: sorted_arg_names.index(kv[0])
|
||||
)
|
||||
))
|
||||
if sorted_args:
|
||||
return sorted_args[1]
|
||||
else:
|
||||
return tuple()
|
||||
|
||||
|
||||
def get_constructor_abi(contract_abi):
|
||||
candidates = [
|
||||
abi for abi in contract_abi if abi['type'] == 'constructor'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user