Skip to content

Commit

Permalink
Merge pull request #155 from pipermerriam/piper/fancier-contract-objects
Browse files Browse the repository at this point in the history
Add additional compiler fields to Contract object.
  • Loading branch information
pipermerriam committed Feb 6, 2017
2 parents 459dc1e + daa2982 commit 95c8240
Show file tree
Hide file tree
Showing 14 changed files with 401 additions and 138 deletions.
8 changes: 4 additions & 4 deletions docs/contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Contracts
Contract Factories
------------------

.. py:class:: Contract(abi, address=None, code=None, code_runtime=None, source=None)
.. py:class:: Contract(address)
The ``Contract`` class is not intended to be used or instantiated directly.
Instead you should use the ``web3.eth.contract(...)`` method to generate
Expand All @@ -35,19 +35,19 @@ Each Contract Factory exposes the following properties.
The contract ABI array.


.. py:attribute:: Contract.code
.. py:attribute:: Contract.bytecode
The contract bytecode string. May be ``None`` if not provided during
factory creation.


.. py:attribute:: Contract.code_runtime
.. py:attribute:: Contract.bytecode_runtime
The runtime part of the contract bytecode string. May be ``None`` if not
provided during factory creation.


.. py:attribute:: Contract.code_runtime
.. py:attribute:: Contract.bytecode_runtime
The runtime part of the contract bytecode string. May be ``None`` if not
provided during factory creation.
Expand Down
39 changes: 24 additions & 15 deletions docs/web3.eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -579,22 +579,31 @@ with the filtering API.
Contracts
---------

.. py:method:: Eth.contract(abi, address=None, code=None, code_runtime=None, source=None)
.. py:method:: Eth.contract(address=None, contract_name=None, ContractFactoryClass=Contract, **contract_factory_kwargs)
If ``address`` is provided then this method will return an instance of the
contract defined by ``abi``.

If ``address`` is ``None`` then this method will return a Contract Factory,
which can be though of as the python class that represents your contract.

The ``abi`` parameter should be an array containing the ABI definition of
the contract functions and events.

The ``code`` parameter should be the full contract bytecode.

The ``code_runtime`` parameter should be the runtime part of the contract bytecode.

The ``source`` parameter should be a string containing the full source code
of the contract.
contract defined by ``abi``. Otherwise the newly created contract class
will be returned.

``contract_name`` will be used as the name of the contract class. If
``None`` then the name of the ``ContractFactoryClass`` will be used.

``ContractFactoryClass`` will be used as the base Contract class.

The following arguments are accepted for contract class creation.

- ``abi``
- ``asm``
- ``ast``
- ``bytecode``
- ``bytecode_runtime``
- ``clone_bin``
- ``dev_doc``
- ``interface``
- ``metadata``
- ``opcodes``
- ``src_map``
- ``src_map_runtime``
- ``user_doc``

See :doc:`./contracts` for more information about how to use contracts.
28 changes: 14 additions & 14 deletions tests/contracts/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def MATH_ABI():
def MathContract(web3, MATH_ABI, MATH_CODE, MATH_RUNTIME, MATH_SOURCE):
return web3.eth.contract(
abi=MATH_ABI,
code=MATH_CODE,
code_runtime=MATH_RUNTIME,
bytecode=MATH_CODE,
bytecode_runtime=MATH_RUNTIME,
source=MATH_SOURCE,
)

Expand Down Expand Up @@ -114,8 +114,8 @@ def SimpleConstructorContract(web3,
SIMPLE_CONSTRUCTOR_ABI):
return web3.eth.contract(
abi=SIMPLE_CONSTRUCTOR_ABI,
code=SIMPLE_CONSTRUCTOR_CODE,
code_runtime=SIMPLE_CONSTRUCTOR_RUNTIME,
bytecode=SIMPLE_CONSTRUCTOR_CODE,
bytecode_runtime=SIMPLE_CONSTRUCTOR_RUNTIME,
source=SIMPLE_CONSTRUCTOR_SOURCE,
)

Expand Down Expand Up @@ -155,8 +155,8 @@ def WithConstructorArgumentsContract(web3,
WITH_CONSTRUCTOR_ARGUMENTS_ABI):
return web3.eth.contract(
abi=WITH_CONSTRUCTOR_ARGUMENTS_ABI,
code=WITH_CONSTRUCTOR_ARGUMENTS_CODE,
code_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME,
bytecode=WITH_CONSTRUCTOR_ARGUMENTS_CODE,
bytecode_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME,
source=WITH_CONSTRUCTOR_ARGUMENTS_SOURCE,
)

Expand Down Expand Up @@ -195,8 +195,8 @@ def WithConstructorAddressArgumentsContract(web3,
WITH_CONSTRUCTOR_ADDRESS_ABI):
return web3.eth.contract(
abi=WITH_CONSTRUCTOR_ADDRESS_ABI,
code=WITH_CONSTRUCTOR_ADDRESS_CODE,
code_runtime=WITH_CONSTRUCTOR_ADDRESS_RUNTIME,
bytecode=WITH_CONSTRUCTOR_ADDRESS_CODE,
bytecode_runtime=WITH_CONSTRUCTOR_ADDRESS_RUNTIME,
source=WITH_CONSTRUCTOR_ADDRESS_SOURCE,
)

Expand Down Expand Up @@ -255,8 +255,8 @@ def STRING_ABI():
@pytest.fixture()
def STRING_CONTRACT(STRING_SOURCE, STRING_CODE, STRING_RUNTIME, STRING_ABI):
return {
'code': STRING_CODE,
'code_runtime': STRING_RUNTIME,
'bytecode': STRING_CODE,
'bytecode_runtime': STRING_RUNTIME,
'abi': STRING_ABI,
'source': STRING_SOURCE,
}
Expand Down Expand Up @@ -382,8 +382,8 @@ def EMITTER(EMITTER_CODE,
EMITTER_ABI,
EMITTER_SOURCE):
return {
'code': EMITTER_CODE,
'code_runtime': EMITTER_RUNTIME,
'bytecode': EMITTER_CODE,
'bytecode_runtime': EMITTER_RUNTIME,
'source': EMITTER_SOURCE,
'abi': EMITTER_ABI,
}
Expand All @@ -404,8 +404,8 @@ def emitter(web3_empty, Emitter, wait_for_transaction, wait_for_block):
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = deploy_receipt['contractAddress']

code = web3.eth.getCode(contract_address)
assert code == Emitter.code_runtime
bytecode = web3.eth.getCode(contract_address)
assert bytecode == Emitter.bytecode_runtime
return Emitter(address=contract_address)


Expand Down
8 changes: 4 additions & 4 deletions tests/contracts/test_contract_class_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ def test_class_construction_sets_class_vars(web3, MATH_ABI, MATH_CODE,
MATH_RUNTIME, MATH_SOURCE):
MathContract = web3.eth.contract(
abi=MATH_ABI,
code=MATH_CODE,
code_runtime=MATH_RUNTIME,
bytecode=MATH_CODE,
bytecode_runtime=MATH_RUNTIME,
source=MATH_SOURCE,
)

assert MathContract.web3 == web3
assert MathContract.code == MATH_CODE
assert MathContract.code_runtime == MATH_RUNTIME
assert MathContract.bytecode == MATH_CODE
assert MathContract.bytecode_runtime == MATH_RUNTIME
assert MathContract.source == MATH_SOURCE


Expand Down
4 changes: 2 additions & 2 deletions tests/contracts/test_contract_estimateGas.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def math_contract(web3, MATH_ABI, MATH_CODE, MATH_RUNTIME, MATH_SOURCE,
wait_for_transaction):
MathContract = web3.eth.contract(
abi=MATH_ABI,
code=MATH_CODE,
code_runtime=MATH_RUNTIME,
bytecode=MATH_CODE,
bytecode_runtime=MATH_RUNTIME,
source=MATH_SOURCE,
)
deploy_txn = MathContract.deploy({'from': web3.eth.coinbase})
Expand Down
4 changes: 2 additions & 2 deletions tests/contracts/test_extracting_event_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block):
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = deploy_receipt['contractAddress']

code = web3.eth.getCode(contract_address)
assert code == Emitter.code_runtime
bytecode = web3.eth.getCode(contract_address)
assert bytecode == Emitter.bytecode_runtime
return Emitter(address=contract_address)


Expand Down
97 changes: 97 additions & 0 deletions tests/contracts/test_legacy_constructor_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import pytest
import warnings
import sys

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),
{'code': '0x1', 'code_runtime': '0x2'},
{'abi': ABI, 'address': ADDRESS, 'bytecode': '0x1', 'bytecode_runtime': '0x2'}),
(
(ABI, ADDRESS, '0x1', '0x2', '0x3'),
{},
{'abi': ABI, 'address': ADDRESS, 'bytecode': '0x1', 'bytecode_runtime': '0x2', 'source': '0x3'},
),
(
tuple(),
{'abi': ABI, 'address': ADDRESS, 'code': '0x1', 'code_runtime': '0x2', 'source': '0x3'},
{'abi': ABI, 'address': ADDRESS, 'bytecode': '0x1', 'bytecode_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


@pytest.mark.skipif(sys.version_info.major == 2, reason="Python2 fails weirdly on this test")
def test_deprecated_properties():
instance = ContactClassForTest(ABI, ADDRESS, '0x1', '0x2', source='0x3')

with pytest.warns(DeprecationWarning):
instance.source

with pytest.warns(DeprecationWarning):
instance.code

with pytest.warns(DeprecationWarning):
instance.code_runtime


@pytest.mark.skipif(sys.version_info.major == 2, reason="Python2 fails weirdly on this test")
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 changes: 8 additions & 0 deletions tests/empty-object/test_empty_object_is_falsy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from web3.utils.empty import (
empty,
)


def test_empty_object_is_falsy():
assert bool(empty) is False
assert not empty
4 changes: 2 additions & 2 deletions tests/eth-module/test_eth_estimateGas.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def math_contract(web3, MATH_ABI, MATH_CODE, MATH_RUNTIME, MATH_SOURCE,
wait_for_transaction):
MathContract = web3.eth.contract(
abi=MATH_ABI,
code=MATH_CODE,
code_runtime=MATH_RUNTIME,
bytecode=MATH_CODE,
bytecode_runtime=MATH_RUNTIME,
source=MATH_SOURCE,
)
deploy_txn = MathContract.deploy({'from': web3.eth.coinbase})
Expand Down
8 changes: 4 additions & 4 deletions tests/filtering/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ def EMITTER(EMITTER_CODE,
EMITTER_ABI,
EMITTER_SOURCE):
return {
'code': EMITTER_CODE,
'code_runtime': EMITTER_RUNTIME,
'bytecode': EMITTER_CODE,
'bytecode_runtime': EMITTER_RUNTIME,
'source': EMITTER_SOURCE,
'abi': EMITTER_ABI,
}
Expand All @@ -142,8 +142,8 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block):
deploy_receipt = wait_for_transaction(web3, deploy_txn_hash)
contract_address = deploy_receipt['contractAddress']

code = web3.eth.getCode(contract_address)
assert code == Emitter.code_runtime
bytecode = web3.eth.getCode(contract_address)
assert bytecode == Emitter.bytecode_runtime
return Emitter(address=contract_address)


Expand Down

0 comments on commit 95c8240

Please sign in to comment.