mirror of
https://github.com/FlipsideCrypto/web3.py.git
synced 2026-02-06 10:56:47 +00:00
Modify web3 contract API to include the extra compiler fields
This commit is contained in:
parent
676e540321
commit
e300e64d83
90
tests/contracts/test_legacy_constructor_adapter.py
Normal file
90
tests/contracts/test_legacy_constructor_adapter.py
Normal file
@ -0,0 +1,90 @@
|
||||
import pytest
|
||||
import warnings
|
||||
|
||||
from web3.contract import (
|
||||
Contract,
|
||||
)
|
||||
|
||||
|
||||
ABI = [
|
||||
{
|
||||
"constant": False,
|
||||
"inputs": [],
|
||||
"name": "func_1",
|
||||
"outputs": [],
|
||||
"type": "function",
|
||||
},
|
||||
]
|
||||
|
||||
ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
|
||||
class ContactClassForTest(Contract):
|
||||
web3 = True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'args,kwargs,expected',
|
||||
(
|
||||
((ABI,), {}, {'abi': ABI}),
|
||||
((ABI,), {'abi': ABI}, TypeError),
|
||||
((ABI, ADDRESS), {}, {'abi': ABI, 'address': ADDRESS}),
|
||||
(
|
||||
(ABI, ADDRESS, '0x1', '0x2', '0x3'),
|
||||
{},
|
||||
{'abi': ABI, 'address': ADDRESS, 'binary': '0x1', 'binary_runtime': '0x2', 'source': '0x3'},
|
||||
),
|
||||
(
|
||||
tuple(),
|
||||
{'abi': ABI, 'address': ADDRESS, 'code': '0x1', 'code_runtime': '0x2', 'source': '0x3'},
|
||||
{'abi': ABI, 'address': ADDRESS, 'binary': '0x1', 'binary_runtime': '0x2', 'source': '0x3'},
|
||||
),
|
||||
((ABI, ADDRESS), {'abi': ABI}, TypeError),
|
||||
((ABI, ADDRESS), {'address': ADDRESS}, TypeError),
|
||||
((ADDRESS,), {}, {'address': ADDRESS}),
|
||||
)
|
||||
)
|
||||
def test_process_legacy_constructor_signature(args, kwargs, expected):
|
||||
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
with pytest.raises(expected):
|
||||
ContactClassForTest(*args, **kwargs)
|
||||
return
|
||||
|
||||
actual = ContactClassForTest(*args, **kwargs)
|
||||
for key, value in expected.items():
|
||||
assert getattr(actual, key) == value
|
||||
|
||||
|
||||
def test_deprecated_properties():
|
||||
instance = ContactClassForTest(ABI, ADDRESS, '0x1', '0x2', '0x3')
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
instance.code
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
instance.code_runtime
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
instance.source
|
||||
|
||||
|
||||
def test_deprecated_instantiation():
|
||||
with pytest.warns(Warning) as record:
|
||||
ContactClassForTest(ADDRESS)
|
||||
ContactClassForTest(address=ADDRESS)
|
||||
warnings.warn(Warning('test'))
|
||||
|
||||
assert len(record) == 1
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ContactClassForTest() # no address
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ContactClassForTest(ABI, ADDRESS) # no address
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ContactClassForTest(ABI) # no address
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ContactClassForTest(code='0x1') # no address
|
||||
8
tests/empty-object/test_empty_object_is_falsy.py
Normal file
8
tests/empty-object/test_empty_object_is_falsy.py
Normal file
@ -0,0 +1,8 @@
|
||||
from web3.utils.empty import (
|
||||
empty,
|
||||
)
|
||||
|
||||
|
||||
def test_empty_object_is_falsy():
|
||||
assert bool(empty) is False
|
||||
assert not empty
|
||||
169
web3/contract.py
169
web3/contract.py
@ -2,6 +2,8 @@
|
||||
|
||||
"""
|
||||
import functools
|
||||
import warnings
|
||||
import itertools
|
||||
|
||||
from eth_abi import (
|
||||
encode_abi,
|
||||
@ -16,6 +18,9 @@ from web3.exceptions import (
|
||||
BadFunctionCallOutput,
|
||||
)
|
||||
|
||||
from web3.utils.address import (
|
||||
is_address,
|
||||
)
|
||||
from web3.utils.abi import (
|
||||
filter_by_type,
|
||||
filter_by_name,
|
||||
@ -61,6 +66,17 @@ from web3.utils.string import (
|
||||
coerce_return_to_text,
|
||||
force_obj_to_bytes,
|
||||
)
|
||||
from web3.utils.types import (
|
||||
is_array,
|
||||
)
|
||||
|
||||
|
||||
DEPRECATED_SIGNATURE_MESSAGE = (
|
||||
"The constructor signature for the `Contract` object has changed. "
|
||||
"Please update your code to reflect the updated function signature: "
|
||||
"'Contract(address)'. To construct contract classes use the "
|
||||
"'Contract.factory(...)' class methog."
|
||||
)
|
||||
|
||||
|
||||
class Contract(object):
|
||||
@ -83,23 +99,33 @@ class Contract(object):
|
||||
# set during class construction
|
||||
web3 = None
|
||||
|
||||
# class properties (overridable at instance level)
|
||||
_abi = None
|
||||
_code = None
|
||||
_code_runtime = None
|
||||
_source = None
|
||||
|
||||
# instance level properties
|
||||
address = None
|
||||
|
||||
# class properties (overridable at instance level)
|
||||
abi = None
|
||||
asm = None
|
||||
ast = None
|
||||
|
||||
binary = None
|
||||
binary_runtime = None
|
||||
clone_bin = None
|
||||
|
||||
dev_doc = None
|
||||
interface = None
|
||||
metadata = None
|
||||
opcodes = None
|
||||
src_map = None
|
||||
src_map_runtime = None
|
||||
user_doc = None
|
||||
|
||||
def __init__(self,
|
||||
abi=empty,
|
||||
address=empty,
|
||||
*args,
|
||||
code=empty,
|
||||
bytecode=empty,
|
||||
runtime=empty,
|
||||
code_runtime=empty,
|
||||
source=empty):
|
||||
source=empty,
|
||||
abi=empty,
|
||||
address=empty):
|
||||
"""Create a new smart contract proxy object.
|
||||
|
||||
:param address: Contract address as 0x hex string
|
||||
@ -108,51 +134,127 @@ class Contract(object):
|
||||
:param code_runtime: Override class level definition
|
||||
:param source: Override class level definition
|
||||
"""
|
||||
if self.web3 is empty:
|
||||
if self.web3 is None:
|
||||
raise AttributeError(
|
||||
'The `Contract` class has not been initialized. Please use the '
|
||||
'`web3.contract` interface to create your contract class.'
|
||||
)
|
||||
|
||||
arg_0, arg_1, arg_2, arg_3, arg_4 = tuple(itertools.chain(
|
||||
args,
|
||||
itertools.repeat(empty, 5),
|
||||
))[:5]
|
||||
|
||||
if is_array(arg_0):
|
||||
if abi:
|
||||
raise TypeError("The 'abi' argument was found twice")
|
||||
abi = arg_0
|
||||
elif is_address(arg_0):
|
||||
if address:
|
||||
raise TypeError("The 'address' argument was found twice")
|
||||
address = arg_0
|
||||
|
||||
if is_address(arg_1):
|
||||
if address:
|
||||
raise TypeError("The 'address' argument was found twice")
|
||||
address = arg_1
|
||||
|
||||
if arg_2:
|
||||
if code:
|
||||
raise TypeError("The 'code' argument was found twice")
|
||||
code = arg_2
|
||||
|
||||
if arg_3:
|
||||
if code_runtime:
|
||||
raise TypeError("The 'code_runtime' argument was found twice")
|
||||
code_runtime = arg_3
|
||||
|
||||
if arg_4:
|
||||
if source:
|
||||
raise TypeError("The 'source' argument was found twice")
|
||||
source = arg_4
|
||||
|
||||
if any((abi, code, code_runtime, source)):
|
||||
warnings.warn(DeprecationWarning(
|
||||
"The arguments abi, code, code_runtime, and source have been "
|
||||
"deprecated and will be removed from the Contract class "
|
||||
"constructor in future releases. Update your code to use the "
|
||||
"Contract.factory method."
|
||||
))
|
||||
|
||||
if abi is not empty:
|
||||
self._abi = abi
|
||||
self.abi = abi
|
||||
if code is not empty:
|
||||
self._code = code
|
||||
self.binary = code
|
||||
if code_runtime is not empty:
|
||||
self._code_runtime = code_runtime
|
||||
self.binary_runtime = code_runtime
|
||||
if source is not empty:
|
||||
self._source = source
|
||||
|
||||
self.address = address
|
||||
if address is not empty:
|
||||
self.address = address
|
||||
else:
|
||||
warnings.warn(DeprecationWarning(
|
||||
"The address argument is now required for contract class "
|
||||
"instantiation. Please update your code to reflect this change"
|
||||
))
|
||||
|
||||
@property
|
||||
def abi(self):
|
||||
if self._abi is not None:
|
||||
return self._abi
|
||||
# TODO: abi can be derived from the contract source.
|
||||
raise AttributeError("No contract abi was specified for thes contract")
|
||||
@classmethod
|
||||
def factory(cls, web3, contract_name=None, **kwargs):
|
||||
if contract_name is None:
|
||||
contract_name = cls.__name__
|
||||
|
||||
kwargs['web3'] = web3
|
||||
|
||||
for key in kwargs:
|
||||
if not hasattr(cls, key):
|
||||
raise AttributeError(
|
||||
"Property {0} not found on contract class. "
|
||||
"`Contract.factory` only accepts keyword arguments which are "
|
||||
"present on the contract class"
|
||||
)
|
||||
return type(contract_name, (cls,), kwargs)
|
||||
|
||||
#
|
||||
# deprecated properties
|
||||
#
|
||||
_source = None
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
if self._code is not None:
|
||||
return self._code
|
||||
# TODO: code can be derived from the contract source.
|
||||
warnings.warn(DeprecationWarning(
|
||||
"The `code` property has been deprecated. You should update your "
|
||||
"code to access this value through `contract.binary`. The `code` "
|
||||
"property will be removed in future releases"
|
||||
))
|
||||
if self.binary is not None:
|
||||
return self.binary
|
||||
raise AttributeError("No contract code was specified for thes contract")
|
||||
|
||||
@property
|
||||
def code_runtime(self):
|
||||
if self._code_runtime is not None:
|
||||
return self._code_runtime
|
||||
# TODO: runtime can be derived from the contract source.
|
||||
raise AttributeError(
|
||||
"No contract code_runtime was specified for thes contract"
|
||||
)
|
||||
warnings.warn(DeprecationWarning(
|
||||
"The `code_runtime` property has been deprecated. You should update your "
|
||||
"code to access this value through `contract.binary_runtime`. The `code_runtime` "
|
||||
"property will be removed in future releases"
|
||||
))
|
||||
if self.binary_runtime is not None:
|
||||
return self.binary_runtime
|
||||
raise AttributeError("No contract code_runtime was specified for thes contract")
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
warnings.warn(DeprecationWarning(
|
||||
"The `source` property has been deprecated and will be removed in "
|
||||
"future releases"
|
||||
))
|
||||
if self._source is not None:
|
||||
return self._source
|
||||
raise AttributeError("No contract source was specified for thes contract")
|
||||
|
||||
#
|
||||
# Contract Methods
|
||||
#
|
||||
@classmethod
|
||||
def deploy(cls, transaction=None, args=None, kwargs=None):
|
||||
"""
|
||||
@ -794,6 +896,11 @@ def construct_contract_factory(web3,
|
||||
:param source: As given by solc compiler
|
||||
:return: Contract class (not instance)
|
||||
"""
|
||||
warnings.warn(DeprecationWarning(
|
||||
"This function has been deprecated. Please use the `Contract.factory` "
|
||||
"method as this function will be removed in future releases"
|
||||
))
|
||||
|
||||
_dict = {
|
||||
'web3': web3,
|
||||
'abi': abi,
|
||||
|
||||
37
web3/eth.py
37
web3/eth.py
@ -1,11 +1,16 @@
|
||||
from web3 import formatters
|
||||
from web3.iban import Iban
|
||||
|
||||
from web3.contract import construct_contract_factory
|
||||
from web3.contract import (
|
||||
Contract,
|
||||
)
|
||||
|
||||
from web3.utils.blocks import (
|
||||
is_predefined_block_number,
|
||||
)
|
||||
from web3.utils.address import (
|
||||
is_address,
|
||||
)
|
||||
from web3.utils.encoding import (
|
||||
to_decimal,
|
||||
encode_hex,
|
||||
@ -334,13 +339,33 @@ class Eth(object):
|
||||
"eth_uninstallFilter", [filter_id],
|
||||
)
|
||||
|
||||
def contract(self, abi, address=None, **kwargs):
|
||||
contract_class = construct_contract_factory(self.web3, abi, **kwargs)
|
||||
def contract(self,
|
||||
*args,
|
||||
contract_name=None,
|
||||
ContractFactoryClass=Contract,
|
||||
**kwargs):
|
||||
has_address = any((
|
||||
'address' in kwargs,
|
||||
len(args) >= 1 and is_address(args[0]),
|
||||
len(args) >= 2 and is_address(args[1]),
|
||||
))
|
||||
|
||||
if address is None:
|
||||
return contract_class
|
||||
if has_address:
|
||||
if 'address' in kwargs:
|
||||
address = kwargs.pop('address')
|
||||
elif is_address(args[0]):
|
||||
address = args[0]
|
||||
elif is_address(args[1]):
|
||||
address = args[1]
|
||||
kwargs['abi'] = args[0]
|
||||
|
||||
return Contract.factory(self.web3, contract_name, **kwargs)(address)
|
||||
else:
|
||||
return contract_class(address=address)
|
||||
try:
|
||||
kwargs['abi'] = args[0]
|
||||
except IndexError:
|
||||
pass
|
||||
return Contract.factory(self.web3, contract_name, **kwargs)
|
||||
|
||||
def getCompilers(self):
|
||||
return self.web3._requestManager.request_blocking("eth_getCompilers", [])
|
||||
|
||||
@ -1,2 +1,9 @@
|
||||
class empty(object):
|
||||
pass
|
||||
class Empty(object):
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
|
||||
|
||||
empty = Empty()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user