Python nested wrapper methods & estimate_gas (#1996)
* git rm unnecessary .gitkeep file * After all Pytest runs, show short test summary * abi-gen/Py: facilitate inlining of parameter lists Effectively, stopped new-lines from being introduced by calls to the `params` and `typed_params` partials. * abi-gen: simple Py wrapper test for local dev'ment * abi-gen/Py: stop gen'ing ValidatorBase * abi-gen/Py: declare abi() wrapper method in Base * abi-gen/Py: methods as classes to ease call/sendTx Represent methods as classes in order to faciliate access to a method's different operations (call, send_transaction, etc). * contract_wrappers.py: make Base methods public Changed some methods on BaseContractWrapper to be public. * contract_wrappers.py: remove unused method * contract_wrappers.py: extract method * abi-gen/Py: inline method * contract_wrappers.py: fix bug in call() We were passing transaction parameters through to sendTransaction() invocations, but not to call() invocations. * abi-gen/Py: remove `view_only` param to call/tx Formerly, in the BaseContractWrapper, there was just one function used for both eth_call and eth_sendTransaction, and you would distinguish between the two by specifying `view_only=True` when you wanted a call. This commit defines a method dedicated to executing an eth_call, and leaves the old method behind, with the `view_only` param removed, to be used for eth_sendTransaction. * abi-gen/Py: rename method * contract_wrappers/Py: simplify web3 func handling Pass web3 function instance into generated wrapper method class constructor, rather than having that class obtain it upon each method call. Really this is just an elimination of a call to BaseContractWrapper.contract_instance(), which will be removed completely in a shortly-upcoming commit. * contract_wrappers.py: inline method Inline and remove method BaseContractWrapper.contract_instance(). * contract_wrappers.py: pass Validator to *Method Pass a ValidatorBase instance into construction of the contract method classes, *Method, to eliminate another dependency on the containing contract object, which will be eliminated completely in a shortly-upcoming commit. * abi-gen/Py: BaseContractWrapper -> ContractMethod Change the fundamental thing-to-be-wrapped from the contract to the method. Since the named method classes were introduced (in a previous commit), and since the operations contained within the Base are predominantly focused on supporting method calls more than anything else, it makes more intuitive sense to provide a base for the methods than for the contract. With this change, the method classes no longer require a contract object to be passed to their constructors. The contract members that the methods were utilizing are now passed directly to the method constructor. * contract_wrappers.py: rename module to bases... ...from _base_contract_wrapper. The old name hasn't made sense since ValidatorBase was moved into that module, and definitely doesn't make sense now that the fundamental thing-to-be-wrapped has changed from the contract to the method. Also renamed to make it public (removed the leading underscore) since we're generating code that will depend on it. * abi-gen/Py: clarify call/sendTx docstrings * abi-gen/Py: adjust whitespace * contract_wrappers.py: inline method * abi-gen/Py: rename class ValidatorBase... ...to just Validator. It's in the "bases" module, which provides the context needed in order to know it's a base class * python-packages: fix silent failures of ./parallel * contract_wrappers.py: remove private_key support Having this present was overcomplicating interfaces. And it was untested (and not readily working when testing was attempted). And it only provided a thin layer of convenience, which a client could easily code up themselves. * contract_wrappers.py: inline method * contract_wrappers.py: rm unused member variables * contract_wrappers.py: rm unnecessary instance var * abi-gen/Py: add estimate_gas to gen'd methods * update CHANGELOG.json
This commit is contained in:
parent
4eb0767834
commit
57318c0041
@ -11,37 +11,18 @@ from typing import ( # pylint: disable=unused-import
|
||||
Union,
|
||||
)
|
||||
|
||||
from eth_utils import to_checksum_address
|
||||
from mypy_extensions import TypedDict # pylint: disable=unused-import
|
||||
from hexbytes import HexBytes
|
||||
from web3 import Web3
|
||||
from web3.contract import ContractFunction
|
||||
from web3.datastructures import AttributeDict
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from zero_ex.contract_wrappers._base_contract_wrapper import BaseContractWrapper
|
||||
from zero_ex.contract_wrappers.bases import ContractMethod, Validator
|
||||
from zero_ex.contract_wrappers.tx_params import TxParams
|
||||
|
||||
|
||||
class {{contractName}}ValidatorBase:
|
||||
"""Base class for validating inputs to {{contractName}} methods."""
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Initialize the instance."""
|
||||
|
||||
def assert_valid(
|
||||
self, method_name: str, parameter_name: str, argument_value: Any
|
||||
):
|
||||
"""Raise an exception if method input is not valid.
|
||||
|
||||
:param method_name: Name of the method whose input is to be validated.
|
||||
:param parameter_name: Name of the parameter whose input is to be
|
||||
validated.
|
||||
:param argument_value: Value of argument to parameter to be validated.
|
||||
"""
|
||||
|
||||
|
||||
# Try to import a custom validator class definition; if there isn't one,
|
||||
# declare one that we can instantiate for the default argument to the
|
||||
# constructor for {{contractName}} below.
|
||||
@ -53,56 +34,52 @@ try:
|
||||
)
|
||||
except ImportError:
|
||||
|
||||
class {{contractName}}Validator({{contractName}}ValidatorBase): # type: ignore
|
||||
class {{contractName}}Validator(Validator): # type: ignore
|
||||
"""No-op input validator."""
|
||||
|
||||
|
||||
{{tupleDefinitions ABIString}}
|
||||
|
||||
{{#each methods}}
|
||||
{{> method_class contractName=../contractName}}
|
||||
{{/each}}
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class {{contractName}}(BaseContractWrapper):
|
||||
# pylint: disable=too-many-public-methods,too-many-instance-attributes
|
||||
class {{contractName}}:
|
||||
"""Wrapper class for {{contractName}} Solidity contract.{{docBytesIfNecessary ABIString}}"""
|
||||
{{#each methods}}
|
||||
{{toPythonIdentifier this.name}}: {{toPythonClassname this.name}}Method
|
||||
{{/each}}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
validator: {{contractName}}Validator = None,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Get an instance of wrapper for smart contract.
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param contract_address: where the contract has been deployed
|
||||
:param private_key: If specified, transactions will be signed locally,
|
||||
via Web3.py's `eth.account.signTransaction()`:code:, before being
|
||||
sent via `eth.sendRawTransaction()`:code:.
|
||||
:param validator: for validation of method inputs.
|
||||
"""
|
||||
super().__init__(
|
||||
provider=provider,
|
||||
contract_address=contract_address,
|
||||
private_key=private_key,
|
||||
)
|
||||
self.contract_address = contract_address
|
||||
|
||||
if not validator:
|
||||
validator = {{contractName}}Validator(provider, contract_address, private_key)
|
||||
validator = {{contractName}}Validator(provider, contract_address)
|
||||
|
||||
self.validator = validator
|
||||
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
|
||||
provider
|
||||
).eth
|
||||
|
||||
def _get_contract_instance(self, token_address):
|
||||
"""Get an instance of the smart contract at a specific address.
|
||||
functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi={{contractName}}.abi()).functions
|
||||
|
||||
:returns: contract object
|
||||
"""
|
||||
return self._contract_instance(
|
||||
address=token_address, abi={{contractName}}.abi()
|
||||
)
|
||||
{{#each methods}}
|
||||
{{> call contractName=../contractName}}
|
||||
self.{{toPythonIdentifier this.name}} = {{toPythonClassname this.name}}Method(provider, contract_address, functions.{{this.name}}, validator)
|
||||
|
||||
{{/each}}
|
||||
{{#each events}}
|
||||
{{> event}}
|
||||
{{> event contractName=../contractName}}
|
||||
{{/each}}
|
||||
|
||||
@staticmethod
|
||||
|
@ -1,54 +0,0 @@
|
||||
|
||||
def {{this.languageSpecificName}}(
|
||||
self,
|
||||
{{> typed_params inputs=inputs}}
|
||||
tx_params: Optional[TxParams] = None,
|
||||
{{^this.constant}}
|
||||
view_only: bool = False,
|
||||
{{/this.constant}}
|
||||
) -> {{> return_type outputs=outputs~}}:
|
||||
"""Execute underlying, same-named contract method.
|
||||
{{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}}
|
||||
{{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}}
|
||||
:param tx_params: transaction parameters
|
||||
{{#if this.constant~}}
|
||||
{{#if this.devdoc.return}}
|
||||
{{makeReturnDocstringRole this.devdoc.return 8}}{{/if}}
|
||||
{{else}}
|
||||
:param view_only: whether to use transact() or call()
|
||||
|
||||
:returns: if param `view_only`:code: is `True`:code:, then returns the
|
||||
value returned from the underlying function; else returns the
|
||||
transaction hash.
|
||||
{{/if}}
|
||||
"""
|
||||
{{#each this.inputs}}
|
||||
self.validator.assert_valid(
|
||||
method_name='{{../name}}',
|
||||
parameter_name='{{name}}',
|
||||
argument_value={{toPythonIdentifier name}},
|
||||
)
|
||||
{{#if (equal type 'address')}}
|
||||
{{toPythonIdentifier this.name}} = self._validate_and_checksum_address({{toPythonIdentifier this.name}})
|
||||
{{else if (equal type 'uint256')}}
|
||||
# safeguard against fractional inputs
|
||||
{{toPythonIdentifier this.name}} = int({{toPythonIdentifier this.name}})
|
||||
{{else if (equal type 'bytes')}}
|
||||
{{toPythonIdentifier this.name}} = bytes.fromhex({{toPythonIdentifier this.name}}.decode("utf-8"))
|
||||
{{else if (equal type 'bytes[]')}}
|
||||
{{toPythonIdentifier this.name}} = [
|
||||
bytes.fromhex({{toPythonIdentifier this.name}}_element.decode("utf-8"))
|
||||
for {{toPythonIdentifier this.name}}_element in {{toPythonIdentifier this.name}}
|
||||
]
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
func = self._get_contract_instance(
|
||||
self.contract_address
|
||||
).functions.{{this.name}}(
|
||||
{{> params}}
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func,
|
||||
tx_params=tx_params,
|
||||
view_only={{#if this.constant}}True{{else}}view_only{{/if}}
|
||||
)
|
@ -6,8 +6,4 @@
|
||||
{{makeEventParameterDocstringRole name 8}}
|
||||
"""
|
||||
tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
|
||||
return (
|
||||
self._get_contract_instance(self.contract_address)
|
||||
.events.{{name}}()
|
||||
.processReceipt(tx_receipt)
|
||||
)
|
||||
return self._web3_eth.contract(address=to_checksum_address(self.contract_address), abi={{contractName}}.abi()).events.{{name}}().processReceipt(tx_receipt)
|
||||
|
@ -0,0 +1,76 @@
|
||||
|
||||
class {{toPythonClassname this.name}}Method(ContractMethod):
|
||||
"""Various interfaces to the {{this.name}} method."""
|
||||
|
||||
def __init__(self, provider: BaseProvider, contract_address: str, contract_function: ContractFunction, validator: Validator=None):
|
||||
"""Persist instance data."""
|
||||
super().__init__(provider, contract_address, validator)
|
||||
self.underlying_method = contract_function
|
||||
|
||||
{{#if inputs}}
|
||||
def validate_and_normalize_inputs(self, {{> typed_params inputs=inputs}}):
|
||||
"""Validate the inputs to the {{this.name}} method."""
|
||||
{{#each this.inputs}}
|
||||
self.validator.assert_valid(
|
||||
method_name='{{../name}}',
|
||||
parameter_name='{{name}}',
|
||||
argument_value={{toPythonIdentifier name}},
|
||||
)
|
||||
{{#if (equal type 'address')}}
|
||||
{{toPythonIdentifier this.name}} = self.validate_and_checksum_address({{toPythonIdentifier this.name}})
|
||||
{{else if (equal type 'uint256')}}
|
||||
# safeguard against fractional inputs
|
||||
{{toPythonIdentifier this.name}} = int({{toPythonIdentifier this.name}})
|
||||
{{else if (equal type 'bytes')}}
|
||||
{{toPythonIdentifier this.name}} = bytes.fromhex({{toPythonIdentifier this.name}}.decode("utf-8"))
|
||||
{{else if (equal type 'bytes[]')}}
|
||||
{{toPythonIdentifier this.name}} = [
|
||||
bytes.fromhex({{toPythonIdentifier this.name}}_element.decode("utf-8"))
|
||||
for {{toPythonIdentifier this.name}}_element in {{toPythonIdentifier this.name}}
|
||||
]
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
return ({{> params }})
|
||||
|
||||
{{/if}}
|
||||
def call(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> {{> return_type outputs=outputs type='call'~}}:
|
||||
"""Execute underlying contract method via eth_call.
|
||||
{{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}}
|
||||
{{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}}
|
||||
:param tx_params: transaction parameters
|
||||
{{#if this.constant~}}
|
||||
{{#if this.devdoc.return}}
|
||||
{{makeReturnDocstringRole this.devdoc.return 8}}{{/if}}
|
||||
{{else}}
|
||||
:returns: the return value of the underlying method.
|
||||
{{/if}}
|
||||
"""
|
||||
{{#if inputs}}
|
||||
({{> params }}) = self.validate_and_normalize_inputs({{> params}})
|
||||
{{/if}}
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method({{> params}}).call(tx_params.as_dict())
|
||||
|
||||
def send_transaction(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> Union[HexBytes, bytes]:
|
||||
"""Execute underlying contract method via eth_sendTransaction.
|
||||
{{sanitizeDevdocDetails this.name this.devdoc.details 8}}{{~#if this.devdoc.params~}}{{#each this.devdoc.params}}
|
||||
{{makeParameterDocstringRole @key this 8}}{{/each}}{{/if}}
|
||||
:param tx_params: transaction parameters
|
||||
{{#if this.constant~}}
|
||||
{{#if this.devdoc.return}}
|
||||
{{makeReturnDocstringRole this.devdoc.return 8}}{{/if}}
|
||||
{{/if}}
|
||||
"""
|
||||
{{#if inputs}}
|
||||
({{> params }}) = self.validate_and_normalize_inputs({{> params}})
|
||||
{{/if}}
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method({{> params}}).transact(tx_params.as_dict())
|
||||
|
||||
def estimate_gas(self, {{#if inputs}}{{> typed_params inputs=inputs}}, {{/if}}tx_params: Optional[TxParams] = None) -> int:
|
||||
"""Estimate gas consumption of method call."""
|
||||
{{#if inputs}}
|
||||
({{> params }}) = self.validate_and_normalize_inputs({{> params}})
|
||||
{{/if}}
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method({{> params}}).estimateGas(tx_params.as_dict())
|
@ -1,3 +1,3 @@
|
||||
{{#each inputs}}
|
||||
{{toPythonIdentifier name}}{{#if @last}}{{else}},{{/if}}
|
||||
{{/each}}
|
||||
{{toPythonIdentifier name}}{{#if @last}}{{else}}, {{/if~}}
|
||||
{{/each~}}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{{#each inputs}}
|
||||
{{toPythonIdentifier name}}: {{#parameterType type components}}{{/parameterType}},
|
||||
{{/each}}
|
||||
{{toPythonIdentifier name}}: {{#parameterType type components}}{{/parameterType}}{{^if @last}}, {{/if~}}
|
||||
{{/each~}}
|
||||
|
@ -1,12 +1,33 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1564604963,
|
||||
"version": "4.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "whitespace changes to generated Python code",
|
||||
"pr": 1996
|
||||
},
|
||||
{
|
||||
"note": "move Python Validator base class from generated code to common package",
|
||||
"pr": 1996
|
||||
},
|
||||
{
|
||||
"note": "Changed fundamental thing-to-be-wrapped from the contract to the contract method. That is, now there is a base contract method wrapper class rather than a base contract wrapper class, and individual contract methods are represented by named classes inheriting from that base, and the different operations on a method are now represented by a nested-object dot notation, ie, WrappedContract.ContractMethod.call() and WrappedContract.ContractMethod.send_transaction().",
|
||||
"pr": 1996
|
||||
},
|
||||
{
|
||||
"note": "added gas estimation functionality to contract methods",
|
||||
"pr": 1996
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "3.1.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
],
|
||||
"timestamp": 1564604963
|
||||
},
|
||||
{
|
||||
"version": "3.1.1",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"test_cli:clean": "rm -rf test-cli/output && rm -rf test-cli/test_typescript/lib",
|
||||
"test_cli:build": "tsc --project test-cli/tsconfig.json",
|
||||
"test_cli:run_mocha": "mocha --require source-map-support/register --require make-promises-safe test-cli/test_typescript/lib/**/*_test.js --timeout 100000 --bail --exit",
|
||||
"test_cli:test_python": "black --check test-cli/output/python/**/__init__.py; test $? -le 1 # just make sure black can parse the output",
|
||||
"rebuild_and_test": "run-s build test",
|
||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||
|
@ -225,6 +225,10 @@ function registerPythonHelpers(): void {
|
||||
}
|
||||
return '';
|
||||
});
|
||||
Handlebars.registerHelper(
|
||||
'toPythonClassname',
|
||||
(sourceName: string) => new Handlebars.SafeString(changeCase.pascal(sourceName)),
|
||||
);
|
||||
}
|
||||
if (args.language === 'TypeScript') {
|
||||
registerTypeScriptHelpers();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,37 +11,18 @@ from typing import ( # pylint: disable=unused-import
|
||||
Union,
|
||||
)
|
||||
|
||||
from eth_utils import to_checksum_address
|
||||
from mypy_extensions import TypedDict # pylint: disable=unused-import
|
||||
from hexbytes import HexBytes
|
||||
from web3 import Web3
|
||||
from web3.contract import ContractFunction
|
||||
from web3.datastructures import AttributeDict
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from zero_ex.contract_wrappers._base_contract_wrapper import BaseContractWrapper
|
||||
from zero_ex.contract_wrappers.bases import ContractMethod, Validator
|
||||
from zero_ex.contract_wrappers.tx_params import TxParams
|
||||
|
||||
|
||||
class LibDummyValidatorBase:
|
||||
"""Base class for validating inputs to LibDummy methods."""
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Initialize the instance."""
|
||||
|
||||
def assert_valid(
|
||||
self, method_name: str, parameter_name: str, argument_value: Any
|
||||
):
|
||||
"""Raise an exception if method input is not valid.
|
||||
|
||||
:param method_name: Name of the method whose input is to be validated.
|
||||
:param parameter_name: Name of the parameter whose input is to be
|
||||
validated.
|
||||
:param argument_value: Value of argument to parameter to be validated.
|
||||
"""
|
||||
|
||||
|
||||
# Try to import a custom validator class definition; if there isn't one,
|
||||
# declare one that we can instantiate for the default argument to the
|
||||
# constructor for LibDummy below.
|
||||
@ -53,15 +34,15 @@ try:
|
||||
)
|
||||
except ImportError:
|
||||
|
||||
class LibDummyValidator(LibDummyValidatorBase): # type: ignore
|
||||
class LibDummyValidator(Validator): # type: ignore
|
||||
"""No-op input validator."""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class LibDummy(BaseContractWrapper):
|
||||
# pylint: disable=too-many-public-methods,too-many-instance-attributes
|
||||
class LibDummy:
|
||||
"""Wrapper class for LibDummy Solidity contract."""
|
||||
|
||||
def __init__(
|
||||
@ -69,35 +50,24 @@ class LibDummy(BaseContractWrapper):
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
validator: LibDummyValidator = None,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Get an instance of wrapper for smart contract.
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param contract_address: where the contract has been deployed
|
||||
:param private_key: If specified, transactions will be signed locally,
|
||||
via Web3.py's `eth.account.signTransaction()`:code:, before being
|
||||
sent via `eth.sendRawTransaction()`:code:.
|
||||
:param validator: for validation of method inputs.
|
||||
"""
|
||||
super().__init__(
|
||||
provider=provider,
|
||||
contract_address=contract_address,
|
||||
private_key=private_key,
|
||||
)
|
||||
self.contract_address = contract_address
|
||||
|
||||
if not validator:
|
||||
validator = LibDummyValidator(provider, contract_address, private_key)
|
||||
validator = LibDummyValidator(provider, contract_address)
|
||||
|
||||
self.validator = validator
|
||||
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
|
||||
provider
|
||||
).eth
|
||||
|
||||
def _get_contract_instance(self, token_address):
|
||||
"""Get an instance of the smart contract at a specific address.
|
||||
functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi=LibDummy.abi()).functions
|
||||
|
||||
:returns: contract object
|
||||
"""
|
||||
return self._contract_instance(
|
||||
address=token_address, abi=LibDummy.abi()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def abi():
|
||||
|
@ -11,37 +11,18 @@ from typing import ( # pylint: disable=unused-import
|
||||
Union,
|
||||
)
|
||||
|
||||
from eth_utils import to_checksum_address
|
||||
from mypy_extensions import TypedDict # pylint: disable=unused-import
|
||||
from hexbytes import HexBytes
|
||||
from web3 import Web3
|
||||
from web3.contract import ContractFunction
|
||||
from web3.datastructures import AttributeDict
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from zero_ex.contract_wrappers._base_contract_wrapper import BaseContractWrapper
|
||||
from zero_ex.contract_wrappers.bases import ContractMethod, Validator
|
||||
from zero_ex.contract_wrappers.tx_params import TxParams
|
||||
|
||||
|
||||
class TestLibDummyValidatorBase:
|
||||
"""Base class for validating inputs to TestLibDummy methods."""
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Initialize the instance."""
|
||||
|
||||
def assert_valid(
|
||||
self, method_name: str, parameter_name: str, argument_value: Any
|
||||
):
|
||||
"""Raise an exception if method input is not valid.
|
||||
|
||||
:param method_name: Name of the method whose input is to be validated.
|
||||
:param parameter_name: Name of the parameter whose input is to be
|
||||
validated.
|
||||
:param argument_value: Value of argument to parameter to be validated.
|
||||
"""
|
||||
|
||||
|
||||
# Try to import a custom validator class definition; if there isn't one,
|
||||
# declare one that we can instantiate for the default argument to the
|
||||
# constructor for TestLibDummy below.
|
||||
@ -53,62 +34,23 @@ try:
|
||||
)
|
||||
except ImportError:
|
||||
|
||||
class TestLibDummyValidator(TestLibDummyValidatorBase): # type: ignore
|
||||
class TestLibDummyValidator(Validator): # type: ignore
|
||||
"""No-op input validator."""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class TestLibDummy(BaseContractWrapper):
|
||||
"""Wrapper class for TestLibDummy Solidity contract."""
|
||||
class PublicAddConstantMethod(ContractMethod):
|
||||
"""Various interfaces to the publicAddConstant method."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
validator: TestLibDummyValidator = None,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Get an instance of wrapper for smart contract.
|
||||
def __init__(self, provider: BaseProvider, contract_address: str, contract_function: ContractFunction, validator: Validator=None):
|
||||
"""Persist instance data."""
|
||||
super().__init__(provider, contract_address, validator)
|
||||
self.underlying_method = contract_function
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param contract_address: where the contract has been deployed
|
||||
:param private_key: If specified, transactions will be signed locally,
|
||||
via Web3.py's `eth.account.signTransaction()`:code:, before being
|
||||
sent via `eth.sendRawTransaction()`:code:.
|
||||
"""
|
||||
super().__init__(
|
||||
provider=provider,
|
||||
contract_address=contract_address,
|
||||
private_key=private_key,
|
||||
)
|
||||
|
||||
if not validator:
|
||||
validator = TestLibDummyValidator(provider, contract_address, private_key)
|
||||
|
||||
self.validator = validator
|
||||
|
||||
def _get_contract_instance(self, token_address):
|
||||
"""Get an instance of the smart contract at a specific address.
|
||||
|
||||
:returns: contract object
|
||||
"""
|
||||
return self._contract_instance(
|
||||
address=token_address, abi=TestLibDummy.abi()
|
||||
)
|
||||
|
||||
def public_add_constant(
|
||||
self,
|
||||
x: int,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
) -> int:
|
||||
"""Execute underlying, same-named contract method.
|
||||
|
||||
:param tx_params: transaction parameters
|
||||
|
||||
"""
|
||||
def validate_and_normalize_inputs(self, x: int):
|
||||
"""Validate the inputs to the publicAddConstant method."""
|
||||
self.validator.assert_valid(
|
||||
method_name='publicAddConstant',
|
||||
parameter_name='x',
|
||||
@ -116,27 +58,44 @@ class TestLibDummy(BaseContractWrapper):
|
||||
)
|
||||
# safeguard against fractional inputs
|
||||
x = int(x)
|
||||
func = self._get_contract_instance(
|
||||
self.contract_address
|
||||
).functions.publicAddConstant(
|
||||
x
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func,
|
||||
tx_params=tx_params,
|
||||
view_only=True
|
||||
)
|
||||
return (x)
|
||||
|
||||
def public_add_one(
|
||||
self,
|
||||
x: int,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
) -> int:
|
||||
"""Execute underlying, same-named contract method.
|
||||
def call(self, x: int, tx_params: Optional[TxParams] = None) -> int:
|
||||
"""Execute underlying contract method via eth_call.
|
||||
|
||||
:param tx_params: transaction parameters
|
||||
|
||||
"""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).call(tx_params.as_dict())
|
||||
|
||||
def send_transaction(self, x: int, tx_params: Optional[TxParams] = None) -> Union[HexBytes, bytes]:
|
||||
"""Execute underlying contract method via eth_sendTransaction.
|
||||
|
||||
:param tx_params: transaction parameters
|
||||
|
||||
"""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).transact(tx_params.as_dict())
|
||||
|
||||
def estimate_gas(self, x: int, tx_params: Optional[TxParams] = None) -> int:
|
||||
"""Estimate gas consumption of method call."""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).estimateGas(tx_params.as_dict())
|
||||
|
||||
class PublicAddOneMethod(ContractMethod):
|
||||
"""Various interfaces to the publicAddOne method."""
|
||||
|
||||
def __init__(self, provider: BaseProvider, contract_address: str, contract_function: ContractFunction, validator: Validator=None):
|
||||
"""Persist instance data."""
|
||||
super().__init__(provider, contract_address, validator)
|
||||
self.underlying_method = contract_function
|
||||
|
||||
def validate_and_normalize_inputs(self, x: int):
|
||||
"""Validate the inputs to the publicAddOne method."""
|
||||
self.validator.assert_valid(
|
||||
method_name='publicAddOne',
|
||||
parameter_name='x',
|
||||
@ -144,16 +103,67 @@ class TestLibDummy(BaseContractWrapper):
|
||||
)
|
||||
# safeguard against fractional inputs
|
||||
x = int(x)
|
||||
func = self._get_contract_instance(
|
||||
self.contract_address
|
||||
).functions.publicAddOne(
|
||||
x
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func,
|
||||
tx_params=tx_params,
|
||||
view_only=True
|
||||
)
|
||||
return (x)
|
||||
|
||||
def call(self, x: int, tx_params: Optional[TxParams] = None) -> int:
|
||||
"""Execute underlying contract method via eth_call.
|
||||
|
||||
:param tx_params: transaction parameters
|
||||
|
||||
"""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).call(tx_params.as_dict())
|
||||
|
||||
def send_transaction(self, x: int, tx_params: Optional[TxParams] = None) -> Union[HexBytes, bytes]:
|
||||
"""Execute underlying contract method via eth_sendTransaction.
|
||||
|
||||
:param tx_params: transaction parameters
|
||||
|
||||
"""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).transact(tx_params.as_dict())
|
||||
|
||||
def estimate_gas(self, x: int, tx_params: Optional[TxParams] = None) -> int:
|
||||
"""Estimate gas consumption of method call."""
|
||||
(x) = self.validate_and_normalize_inputs(x)
|
||||
tx_params = super().normalize_tx_params(tx_params)
|
||||
return self.underlying_method(x).estimateGas(tx_params.as_dict())
|
||||
|
||||
# pylint: disable=too-many-public-methods,too-many-instance-attributes
|
||||
class TestLibDummy:
|
||||
"""Wrapper class for TestLibDummy Solidity contract."""
|
||||
public_add_constant: PublicAddConstantMethod
|
||||
public_add_one: PublicAddOneMethod
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
validator: TestLibDummyValidator = None,
|
||||
):
|
||||
"""Get an instance of wrapper for smart contract.
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param contract_address: where the contract has been deployed
|
||||
:param validator: for validation of method inputs.
|
||||
"""
|
||||
self.contract_address = contract_address
|
||||
|
||||
if not validator:
|
||||
validator = TestLibDummyValidator(provider, contract_address)
|
||||
|
||||
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
|
||||
provider
|
||||
).eth
|
||||
|
||||
functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi=TestLibDummy.abi()).functions
|
||||
|
||||
self.public_add_constant = PublicAddConstantMethod(provider, contract_address, functions.publicAddConstant, validator)
|
||||
|
||||
self.public_add_one = PublicAddOneMethod(provider, contract_address, functions.publicAddOne, validator)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def abi():
|
||||
|
@ -109,7 +109,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
with open("README.md", "r") as file_handle:
|
||||
|
@ -138,7 +138,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
with open("README.md", "r") as file_handle:
|
||||
|
5
python-packages/contract_wrappers/setup.cfg
Normal file
5
python-packages/contract_wrappers/setup.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[pycodestyle]
|
||||
ignore = E501, W503
|
||||
# E501 = line too long
|
||||
# W503 = line break occurred before a binary operator
|
||||
# we let black handle these things
|
@ -74,7 +74,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
|
@ -102,13 +102,13 @@ balance:
|
||||
|
||||
>>> erc20_proxy_addr = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
|
||||
|
||||
>>> tx = zrx_token.approve(
|
||||
>>> tx = zrx_token.approve.send_transaction(
|
||||
... erc20_proxy_addr,
|
||||
... to_wei(100, 'ether'),
|
||||
... tx_params=TxParams(from_=maker_address),
|
||||
... )
|
||||
|
||||
>>> tx = weth_token.approve(
|
||||
>>> tx = weth_token.approve.send_transaction(
|
||||
... erc20_proxy_addr,
|
||||
... to_wei(100, 'ether'),
|
||||
... tx_params=TxParams(from_=taker_address),
|
||||
@ -166,7 +166,7 @@ too.
|
||||
... provider=ganache,
|
||||
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
|
||||
... )
|
||||
>>> tx_hash = exchange.fill_order(
|
||||
>>> tx_hash = exchange.fill_order.send_transaction(
|
||||
... order=order,
|
||||
... taker_asset_fill_amount=order["takerAssetAmount"],
|
||||
... signature=maker_signature,
|
||||
@ -217,7 +217,7 @@ A Maker can cancel an order that has yet to be filled.
|
||||
... )
|
||||
... )
|
||||
|
||||
>>> tx_hash = exchange.cancel_order(
|
||||
>>> tx_hash = exchange.cancel_order.send_transaction(
|
||||
... order=order, tx_params=TxParams(from_=maker_address)
|
||||
... )
|
||||
|
||||
@ -287,12 +287,40 @@ is an example where the taker fills two orders in one transaction:
|
||||
|
||||
Fill order_1 and order_2 together:
|
||||
|
||||
>>> exchange.batch_fill_orders(
|
||||
>>> exchange.batch_fill_orders.send_transaction(
|
||||
... orders=[order_1, order_2],
|
||||
... taker_asset_fill_amounts=[1, 2],
|
||||
... signatures=[signature_1, signature_2],
|
||||
... tx_params=TxParams(from_=taker_address))
|
||||
HexBytes('0x...')
|
||||
|
||||
Estimating gas consumption
|
||||
--------------------------
|
||||
|
||||
Before executing a transaction, you may want to get an estimate of how much gas
|
||||
will be consumed.
|
||||
|
||||
>>> exchange.cancel_order.estimate_gas(
|
||||
... order=Order(
|
||||
... makerAddress=maker_address,
|
||||
... takerAddress='0x0000000000000000000000000000000000000000',
|
||||
... exchangeAddress=exchange_address,
|
||||
... senderAddress='0x0000000000000000000000000000000000000000',
|
||||
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
|
||||
... makerAssetData=asset_data_utils.encode_erc20(weth_address),
|
||||
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
|
||||
... salt=random.randint(1, 100000000000000000),
|
||||
... makerFee=0,
|
||||
... takerFee=0,
|
||||
... makerAssetAmount=1000000000000000000,
|
||||
... takerAssetAmount=500000000000000000000,
|
||||
... expirationTimeSeconds=round(
|
||||
... (datetime.utcnow() + timedelta(days=1)).timestamp()
|
||||
... )
|
||||
... ),
|
||||
... tx_params=TxParams(from_=maker_address),
|
||||
... )
|
||||
73825
|
||||
"""
|
||||
|
||||
from .tx_params import TxParams
|
||||
|
@ -1,136 +0,0 @@
|
||||
"""Base wrapper class for accessing ethereum smart contracts."""
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from eth_utils import to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from .tx_params import TxParams
|
||||
|
||||
|
||||
class BaseContractWrapper:
|
||||
"""Base class for wrapping ethereum smart contracts.
|
||||
|
||||
It provides functionality for instantiating a contract instance,
|
||||
calling view functions, and calling functions which require
|
||||
transactions.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Create an instance of BaseContractWrapper.
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param private_key: If specified, transactions will be signed locally,
|
||||
via Web3.py's `eth.account.signTransaction()`:code:, before being
|
||||
sent via `eth.sendRawTransaction()`:code:.
|
||||
"""
|
||||
self._provider = provider
|
||||
self._private_key = private_key
|
||||
self._web3 = Web3(provider)
|
||||
self._web3_eth = self._web3.eth # pylint: disable=no-member
|
||||
self.contract_address = self._validate_and_checksum_address(
|
||||
contract_address
|
||||
)
|
||||
|
||||
self._can_send_tx = False
|
||||
if self._web3_eth.defaultAccount or self._web3_eth.accounts:
|
||||
self._can_send_tx = True
|
||||
else:
|
||||
middleware_stack = getattr(self._web3, "middleware_stack")
|
||||
if middleware_stack.get("sign_and_send_raw_middleware"):
|
||||
self._can_send_tx = True
|
||||
elif private_key:
|
||||
self._private_key = private_key
|
||||
self._web3_eth.defaultAccount = to_checksum_address(
|
||||
self._web3_eth.account.privateKeyToAccount(
|
||||
private_key
|
||||
).address
|
||||
)
|
||||
self._can_send_tx = True
|
||||
|
||||
def _contract_instance(self, address: str, abi: dict):
|
||||
"""Get a contract instance.
|
||||
|
||||
:param address: string address of contract
|
||||
:param abi: dict contract ABI
|
||||
|
||||
:returns: instance of contract
|
||||
"""
|
||||
return self._web3_eth.contract(
|
||||
address=to_checksum_address(address), abi=abi
|
||||
)
|
||||
|
||||
def _validate_and_checksum_address(self, address: str):
|
||||
if not self._web3.isAddress(address):
|
||||
raise TypeError("Invalid address provided: {}".format(address))
|
||||
return to_checksum_address(address)
|
||||
|
||||
def _invoke_function_call(self, func, tx_params, view_only):
|
||||
if view_only:
|
||||
return func.call()
|
||||
if not self._can_send_tx:
|
||||
raise Exception(
|
||||
"Cannot send transaction because no local private_key"
|
||||
" or account found."
|
||||
)
|
||||
if not tx_params:
|
||||
tx_params = TxParams()
|
||||
if not tx_params.from_:
|
||||
tx_params.from_ = (
|
||||
self._web3_eth.defaultAccount or self._web3_eth.accounts[0]
|
||||
)
|
||||
tx_params.from_ = self._validate_and_checksum_address(tx_params.from_)
|
||||
if self._private_key:
|
||||
res = self._sign_and_send_raw_direct(func, tx_params)
|
||||
else:
|
||||
res = func.transact(tx_params.as_dict())
|
||||
return res
|
||||
|
||||
def _sign_and_send_raw_direct(self, func, tx_params):
|
||||
transaction = func.buildTransaction(tx_params.as_dict())
|
||||
signed_tx = self._web3_eth.account.signTransaction(
|
||||
transaction, private_key=self._private_key
|
||||
)
|
||||
return self._web3_eth.sendRawTransaction(signed_tx.rawTransaction)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def execute_method(
|
||||
self,
|
||||
abi: dict,
|
||||
method: str,
|
||||
args: Optional[Union[list, tuple]] = None,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> str:
|
||||
"""Execute the method on a contract instance.
|
||||
|
||||
:param abi: dict of contract ABI
|
||||
:param method: string name of method to call
|
||||
:param args: default None, list or tuple of arguments for the method
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether the transaction
|
||||
should only be validated.
|
||||
|
||||
:returns: str of transaction hash
|
||||
"""
|
||||
contract_instance = self._contract_instance(
|
||||
address=self.contract_address, abi=abi
|
||||
)
|
||||
if args is None:
|
||||
args = []
|
||||
if hasattr(contract_instance.functions, method):
|
||||
func = getattr(contract_instance.functions, method)(*args)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
raise Exception(
|
||||
"No method {} found on contract {}.".format(
|
||||
self.contract_address, method
|
||||
)
|
||||
)
|
@ -0,0 +1,66 @@
|
||||
"""Base wrapper class for accessing ethereum smart contracts."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from eth_utils import is_address, to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from .tx_params import TxParams
|
||||
|
||||
|
||||
class Validator:
|
||||
"""Base class for validating inputs to methods."""
|
||||
|
||||
def __init__(self, provider: BaseProvider, contract_address: str):
|
||||
"""Initialize the instance."""
|
||||
|
||||
def assert_valid(
|
||||
self, method_name: str, parameter_name: str, argument_value: Any
|
||||
):
|
||||
"""Raise an exception if method input is not valid.
|
||||
|
||||
:param method_name: Name of the method whose input is to be validated.
|
||||
:param parameter_name: Name of the parameter whose input is to be
|
||||
validated.
|
||||
:param argument_value: Value of argument to parameter to be validated.
|
||||
"""
|
||||
|
||||
|
||||
class ContractMethod:
|
||||
"""Base class for wrapping an Ethereum smart contract method."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
validator: Validator = None,
|
||||
):
|
||||
"""Instantiate the object.
|
||||
|
||||
:param provider: Instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param contract_address: Where the contract has been deployed to.
|
||||
:param validator: Used to validate method inputs.
|
||||
"""
|
||||
self._web3_eth = Web3(provider).eth # pylint: disable=no-member
|
||||
if validator is None:
|
||||
validator = Validator(provider, contract_address)
|
||||
self.validator = validator
|
||||
|
||||
@staticmethod
|
||||
def validate_and_checksum_address(address: str):
|
||||
"""Validate the given address, and return it's checksum address."""
|
||||
if not is_address(address):
|
||||
raise TypeError("Invalid address provided: {}".format(address))
|
||||
return to_checksum_address(address)
|
||||
|
||||
def normalize_tx_params(self, tx_params) -> TxParams:
|
||||
"""Normalize and return the given transaction parameters."""
|
||||
if not tx_params:
|
||||
tx_params = TxParams()
|
||||
if not tx_params.from_:
|
||||
tx_params.from_ = (
|
||||
self._web3_eth.defaultAccount or self._web3_eth.accounts[0]
|
||||
)
|
||||
tx_params.from_ = self.validate_and_checksum_address(tx_params.from_)
|
||||
return tx_params
|
@ -6,21 +6,16 @@ from web3.providers.base import BaseProvider
|
||||
|
||||
from zero_ex import json_schemas
|
||||
|
||||
from . import ExchangeValidatorBase
|
||||
from ..bases import Validator
|
||||
from .types import order_to_jsdict
|
||||
|
||||
|
||||
class ExchangeValidator(ExchangeValidatorBase):
|
||||
class ExchangeValidator(Validator):
|
||||
"""Validate inputs to Exchange methods."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
def __init__(self, provider: BaseProvider, contract_address: str):
|
||||
"""Initialize the class."""
|
||||
super().__init__(provider, contract_address, private_key)
|
||||
super().__init__(provider, contract_address)
|
||||
self.contract_address = contract_address
|
||||
|
||||
def assert_valid(
|
||||
|
@ -1,3 +1,7 @@
|
||||
from typing import Union
|
||||
|
||||
def to_checksum_address(address: str) -> str: ...
|
||||
|
||||
def remove_0x_prefix(hex_string: str) -> str: ...
|
||||
|
||||
def is_address(address: Union[str, bytes]) -> bool: ...
|
||||
|
@ -0,0 +1,5 @@
|
||||
class ContractFunction:
|
||||
def __call__(self, *args, **kwargs):
|
||||
...
|
||||
|
||||
...
|
@ -1,3 +1,10 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Contract:
|
||||
def call(self): ...
|
||||
|
||||
functions: Any
|
||||
|
||||
events: Any
|
||||
...
|
||||
|
@ -0,0 +1,15 @@
|
||||
"""Tests for :class:`ContractMethod`."""
|
||||
|
||||
import pytest
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
from zero_ex.contract_wrappers.bases import ContractMethod
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def contract_wrapper(ganache_provider):
|
||||
"""Get a ContractMethod instance for testing."""
|
||||
return ContractMethod(
|
||||
provider=ganache_provider,
|
||||
contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token,
|
||||
)
|
@ -1,48 +0,0 @@
|
||||
"""Tests for :class:`BaseContractWrapper`."""
|
||||
|
||||
import pytest
|
||||
from eth_utils import to_checksum_address
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
from zero_ex.contract_artifacts import abi_by_name
|
||||
from zero_ex.contract_wrappers._base_contract_wrapper import (
|
||||
BaseContractWrapper,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def contract_wrapper(ganache_provider):
|
||||
"""Get a BaseContractWrapper instance for testing."""
|
||||
return BaseContractWrapper(
|
||||
provider=ganache_provider,
|
||||
contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token,
|
||||
)
|
||||
|
||||
|
||||
def test_contract_wrapper__execute_method(
|
||||
accounts,
|
||||
contract_wrapper, # pylint: disable=redefined-outer-name
|
||||
erc20_proxy_address,
|
||||
):
|
||||
"""Test :function:`BaseContractWrapper.execute` method."""
|
||||
acc1_allowance = contract_wrapper.execute_method(
|
||||
abi=abi_by_name("WETH9"),
|
||||
method="allowance",
|
||||
view_only=True,
|
||||
args=(
|
||||
to_checksum_address(accounts[3]),
|
||||
to_checksum_address(erc20_proxy_address),
|
||||
),
|
||||
)
|
||||
assert acc1_allowance == 0
|
||||
|
||||
with pytest.raises(Exception):
|
||||
contract_wrapper.execute_method(
|
||||
abi=abi_by_name("WETH9"),
|
||||
method="send",
|
||||
view_only=True,
|
||||
args=[
|
||||
to_checksum_address(accounts[3]),
|
||||
to_checksum_address(erc20_proxy_address),
|
||||
],
|
||||
)
|
@ -25,8 +25,8 @@ def test_erc20_wrapper__balance_of(
|
||||
weth_instance, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test getting baance of an account for an ERC20 token."""
|
||||
acc1_original_weth_balance = erc20_wrapper.balance_of(accounts[0])
|
||||
acc2_original_weth_balance = erc20_wrapper.balance_of(accounts[1])
|
||||
acc1_original_weth_balance = erc20_wrapper.balance_of.call(accounts[0])
|
||||
acc2_original_weth_balance = erc20_wrapper.balance_of.call(accounts[1])
|
||||
|
||||
expected_difference = 1 * 10 ** 18
|
||||
|
||||
@ -36,8 +36,8 @@ def test_erc20_wrapper__balance_of(
|
||||
weth_instance.functions.deposit().transact(
|
||||
{"from": accounts[1], "value": expected_difference}
|
||||
)
|
||||
acc1_weth_balance = erc20_wrapper.balance_of(accounts[0])
|
||||
acc2_weth_balance = erc20_wrapper.balance_of(accounts[1])
|
||||
acc1_weth_balance = erc20_wrapper.balance_of.call(accounts[0])
|
||||
acc2_weth_balance = erc20_wrapper.balance_of.call(accounts[1])
|
||||
|
||||
assert (
|
||||
acc1_weth_balance - acc1_original_weth_balance == expected_difference
|
||||
@ -53,21 +53,21 @@ def test_erc20_wrapper__approve(
|
||||
erc20_wrapper, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test approving one account to spend balance from another account."""
|
||||
erc20_wrapper.approve(
|
||||
erc20_wrapper.approve.send_transaction(
|
||||
erc20_proxy_address,
|
||||
MAX_ALLOWANCE,
|
||||
tx_params=TxParams(from_=accounts[0]),
|
||||
)
|
||||
erc20_wrapper.approve(
|
||||
erc20_wrapper.approve.send_transaction(
|
||||
erc20_proxy_address,
|
||||
MAX_ALLOWANCE,
|
||||
tx_params=TxParams(from_=accounts[1]),
|
||||
)
|
||||
|
||||
acc_1_weth_allowance = erc20_wrapper.allowance(
|
||||
acc_1_weth_allowance = erc20_wrapper.allowance.call(
|
||||
accounts[0], erc20_proxy_address
|
||||
)
|
||||
acc_2_weth_allowance = erc20_wrapper.allowance(
|
||||
acc_2_weth_allowance = erc20_wrapper.allowance.call(
|
||||
accounts[1], erc20_proxy_address
|
||||
)
|
||||
|
||||
|
@ -77,7 +77,7 @@ def test_exchange_wrapper__fill_order(
|
||||
)
|
||||
order_signature = sign_hash_to_bytes(ganache_provider, maker, order_hash)
|
||||
|
||||
tx_hash = exchange_wrapper.fill_order(
|
||||
tx_hash = exchange_wrapper.fill_order.send_transaction(
|
||||
order=order,
|
||||
taker_asset_fill_amount=order["takerAssetAmount"],
|
||||
signature=order_signature,
|
||||
@ -114,7 +114,7 @@ def test_exchange_wrapper__batch_fill_orders(
|
||||
for order_hash in order_hashes
|
||||
]
|
||||
taker_amounts = [order["takerAssetAmount"] for order in orders]
|
||||
tx_hash = exchange_wrapper.batch_fill_orders(
|
||||
tx_hash = exchange_wrapper.batch_fill_orders.send_transaction(
|
||||
orders=orders,
|
||||
taker_asset_fill_amounts=taker_amounts,
|
||||
signatures=order_signatures,
|
||||
|
@ -40,7 +40,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
|
@ -20,7 +20,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
|
@ -21,7 +21,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
|
@ -20,9 +20,9 @@ $ ./parallel pip uninstall $(basename $(pwd))
|
||||
|
||||
>>>"""
|
||||
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from concurrent.futures import ProcessPoolExecutor, wait
|
||||
from os import chdir
|
||||
from subprocess import check_call
|
||||
from subprocess import CalledProcessError, check_output
|
||||
from sys import argv
|
||||
|
||||
PACKAGES = [
|
||||
@ -38,7 +38,18 @@ PACKAGES = [
|
||||
def run_cmd_on_package(package: str):
|
||||
"""cd to the package dir, ./setup.py lint, cd .."""
|
||||
chdir(package)
|
||||
check_call(f"{' '.join(argv[1:])}".split())
|
||||
try:
|
||||
check_output(f"{' '.join(argv[1:])}".split())
|
||||
except CalledProcessError as error:
|
||||
print(f"standard output from command:\n{error.output.decode('utf-8')}")
|
||||
raise RuntimeError(f"Above exception raised in {package}, ") from error
|
||||
finally:
|
||||
chdir("..")
|
||||
|
||||
ProcessPoolExecutor().map(run_cmd_on_package, PACKAGES)
|
||||
with ProcessPoolExecutor() as executor:
|
||||
for future in executor.map(run_cmd_on_package, PACKAGES):
|
||||
# iterate over map()'s return value, to resolve the futures.
|
||||
# but we don't actually care what the return values are, so just `pass`.
|
||||
# if any exceptions were raised by the underlying task, they'll be
|
||||
# raised as the iteration encounters them.
|
||||
pass
|
||||
|
@ -33,7 +33,8 @@ class TestCommandExtension(TestCommand):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
exit(pytest.main(["--doctest-modules", "-rapP"]))
|
||||
# show short test summary at end ^
|
||||
|
||||
|
||||
class TestPublishCommand(distutils.command.build_py.build_py):
|
||||
|
@ -319,7 +319,7 @@ book. Now let's have the taker fill it:
|
||||
... provider=eth_node,
|
||||
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange
|
||||
... )
|
||||
>>> exchange.fill_order(
|
||||
>>> exchange.fill_order.send_transaction(
|
||||
... order=order,
|
||||
... taker_asset_fill_amount=order['makerAssetAmount']/2, # note the half fill
|
||||
... signature=order['signature'].replace('0x', '').encode('utf-8'),
|
||||
@ -333,7 +333,7 @@ Cancelling
|
||||
Note that the above fill was partial: it only filled half of the order. Now
|
||||
we'll have our maker cancel the remaining order:
|
||||
|
||||
>>> exchange.cancel_order(
|
||||
>>> exchange.cancel_order.send_transaction(
|
||||
... order=order,
|
||||
... tx_params=TxParams(from_=maker_address)
|
||||
... )
|
||||
|
Loading…
x
Reference in New Issue
Block a user