diff --git a/.gitignore b/.gitignore index 6a472f4..27d7898 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,150 @@ +__pycache__ +.env +.history +.hypothesis/ +build/* +reports/* + +.project +.pydevproject +.vscode + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Unit test reports +**/junit-*.xml +**/junit-*.html + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ .eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ *.egg-info/ -dist/ -build/ -.pytest_cache/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ .coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# mac OS +.DS_Store +tests/.DS_Store tests/contracts/build diff --git a/eip712_structs/struct.py b/eip712_structs/struct.py index b754987..b6d35cf 100644 --- a/eip712_structs/struct.py +++ b/eip712_structs/struct.py @@ -2,7 +2,9 @@ import json import operator import re +import typing from collections import OrderedDict, defaultdict +from hexbytes import HexBytes from typing import List, Tuple, NamedTuple from eth_utils.crypto import keccak @@ -84,17 +86,30 @@ def data_dict(self): for k, v in self.values.items(): if isinstance(v, EIP712Struct): result[k] = v.data_dict() + elif isinstance(v, list) and len(v) and isinstance(v[0], EIP712Struct): + result[k] = [ e.data_dict() for e in v ] else: result[k] = v return result + def as_function_args(self) -> Tuple: + result = list() + for v in self.values.values(): + if isinstance(v, EIP712Struct): + result.append(v.as_function_args()) + elif isinstance(v, list) and len(v) and isinstance(v[0], EIP712Struct): + result.append([e.as_function_args() for e in v]) + else: + result.append(v) + return tuple(result) + @classmethod def _encode_type(cls, resolve_references: bool) -> str: member_sigs = [f'{typ.type_name} {name}' for name, typ in cls.get_members()] struct_sig = f'{cls.type_name}({",".join(member_sigs)})' if resolve_references: - reference_structs = set() + reference_structs = list() cls._gather_reference_structs(reference_structs) sorted_structs = sorted(list(s for s in reference_structs if s != cls), key=lambda s: s.type_name) for struct in sorted_structs: @@ -105,10 +120,16 @@ def _encode_type(cls, resolve_references: bool) -> str: def _gather_reference_structs(cls, struct_set): """Finds reference structs defined in this struct type, and inserts them into the given set. """ - structs = [m[1] for m in cls.get_members() if isinstance(m[1], type) and issubclass(m[1], EIP712Struct)] + structs = [ + m[1] for m in cls.get_members() + if isinstance(m[1], type) and issubclass(m[1], EIP712Struct) + ] + [ + m[1].member_type for m in cls.get_members() + if isinstance(m[1], Array) and hasattr(m[1].member_type, "encode_type") + ] for struct in structs: if struct not in struct_set: - struct_set.add(struct) + struct_set.append(struct) struct._gather_reference_structs(struct_set) @classmethod @@ -148,7 +169,9 @@ def _assert_domain(domain): raise ValueError('Domain must be provided, or eip712_structs.default_domain must be set.') return result - def to_message(self, domain: 'EIP712Struct' = None) -> dict: + def to_message( + self, domain: typing.Optional["EIP712Struct"] = None + ) -> dict: """Convert a struct into a dictionary suitable for messaging. Dictionary is of the form: @@ -162,7 +185,7 @@ def to_message(self, domain: 'EIP712Struct' = None) -> dict: :returns: This struct + the domain in dict form, structured as specified for EIP712 messages. """ domain = self._assert_domain(domain) - structs = {domain, self} + structs = [domain, self] self._gather_reference_structs(structs) # Build type dictionary @@ -187,7 +210,9 @@ def to_message_json(self, domain: 'EIP712Struct' = None) -> str: message = self.to_message(domain) return json.dumps(message, cls=BytesJSONEncoder) - def signable_bytes(self, domain: 'EIP712Struct' = None) -> bytes: + def signable_bytes( + self, domain: typing.Optional[typing.Union["EIP712Struct", bytes, str]] = None + ) -> bytes: """Return a ``bytes`` object suitable for signing, as specified for EIP712. As per the spec, bytes are constructed as follows: @@ -196,8 +221,26 @@ def signable_bytes(self, domain: 'EIP712Struct' = None) -> bytes: :param domain: The domain to include in the hash bytes. If None, uses ``eip712_structs.default_domain`` :return: The bytes object """ - domain = self._assert_domain(domain) - result = b'\x19\x01' + domain.hash_struct() + self.hash_struct() + + if isinstance(domain, str): + try: + if domain.startswith('0x'): + domain = HexBytes(domain) + elif domain.startswith('{') and domain.endswith('}'): + domain = json.loads(domain) + else: + raise ValueError + except Exception as ex: + raise ValueError(f"Invalid EIP domain '{domain}'") from ex + + if isinstance(domain, bytes): + if len(domain) != 32: + raise ValueError(f"Domain must be 32 bytes, but was {len(domain)}") + domain_hash = domain + else: + domain_hash: bytes = self._assert_domain(domain).hash_struct() + + result = b"\x19\x01" + domain_hash + self.hash_struct() return result @classmethod @@ -314,6 +357,9 @@ def __eq__(self, other): def __hash__(self): value_hashes = [hash(k) ^ hash(v) for k, v in self.values.items()] return functools.reduce(operator.xor, value_hashes, hash(self.type_name)) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({str(self.data_dict())})" class StructTuple(NamedTuple): diff --git a/eip712_structs/types.py b/eip712_structs/types.py index 0ab615a..209a5bf 100644 --- a/eip712_structs/types.py +++ b/eip712_structs/types.py @@ -64,7 +64,10 @@ def __init__(self, member_type: Union[EIP712Type, Type[EIP712Type]], fixed_lengt def _encode_value(self, value): """Arrays are encoded by concatenating their encoded contents, and taking the keccak256 hash.""" encoder = self.member_type - encoded_values = [encoder.encode_value(v) for v in value] + if hasattr(encoder, "hash_struct"): + encoded_values = [v.hash_struct() for v in value] + else: + encoded_values = [encoder.encode_value(v) for v in value] return keccak(b''.join(encoded_values)) diff --git a/setup.py b/setup.py index c35978c..f44dbfc 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ NAME = 'eip712-structs' -VERSION = '1.1.0' +VERSION = '1001.1.0' install_requirements = [ 'eth-utils>=1.4.0', @@ -15,10 +15,10 @@ ] test_requirements = [ - 'coveralls==1.8.0', - 'pytest==4.6.2', - 'pytest-cov==2.7.1', - 'web3==4.9.2', + 'coveralls>=1.8.0', + 'pytest==6.2.5', + 'pytest-cov==4.0.0', + 'web3==5.31.3', ] @@ -70,6 +70,9 @@ def run_tests(self): packages=find_packages(), install_requires=install_requirements, tests_require=test_requirements, + extras_require={ + "test": test_requirements, + }, cmdclass={ "test": PyTest, "coveralls": CoverallsCommand, diff --git a/tests/contracts/TestContract.abi b/tests/contracts/TestContract.abi new file mode 100644 index 0000000..4b6b85f --- /dev/null +++ b/tests/contracts/TestContract.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"BarSig","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Bar_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FooSig","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"Foo_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1[]","name":"arr","type":"bytes1[]"}],"name":"encodeBytes1Array","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"bar_uint","type":"uint256"}],"internalType":"struct TestContract.Bar","name":"bar","type":"tuple"}],"name":"hashBarStruct","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"bar_uint","type":"uint256"}],"name":"hashBarStructFromParams","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"s","type":"string"},{"internalType":"uint256","name":"u_i","type":"uint256"},{"internalType":"int8","name":"s_i","type":"int8"},{"internalType":"address","name":"a","type":"address"},{"internalType":"bool","name":"b","type":"bool"},{"internalType":"bytes30","name":"bytes_30","type":"bytes30"},{"internalType":"bytes","name":"dyn_bytes","type":"bytes"},{"components":[{"internalType":"uint256","name":"bar_uint","type":"uint256"}],"internalType":"struct TestContract.Bar","name":"bar","type":"tuple"},{"internalType":"bytes1[]","name":"arr","type":"bytes1[]"}],"internalType":"struct TestContract.Foo","name":"foo","type":"tuple"}],"name":"hashFooStruct","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"s","type":"string"},{"internalType":"uint256","name":"u_i","type":"uint256"},{"internalType":"int8","name":"s_i","type":"int8"},{"internalType":"address","name":"a","type":"address"},{"internalType":"bool","name":"b","type":"bool"},{"internalType":"bytes30","name":"bytes_30","type":"bytes30"},{"internalType":"bytes","name":"dyn_bytes","type":"bytes"},{"internalType":"uint256","name":"bar_uint","type":"uint256"},{"internalType":"bytes1[]","name":"arr","type":"bytes1[]"}],"name":"hashFooStructFromParams","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/tests/contracts/TestContract.bin b/tests/contracts/TestContract.bin new file mode 100644 index 0000000..ac0c2e4 --- /dev/null +++ b/tests/contracts/TestContract.bin @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b506113d58061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610091575f3560e01c8063adc69fbb11610064578063adc69fbb14610131578063c10ae04314610161578063c10d805b1461017f578063c2400637146101af578063e0c37e21146101cd57610091565b8063466789eb146100955780634765ed4d146100c55780634d07a25c146100f5578063a976837c14610113575b5f80fd5b6100af60048036038101906100aa9190610690565b6101fd565b6040516100bc91906106d3565b60405180910390f35b6100df60048036038101906100da9190610af8565b610220565b6040516100ec91906106d3565b60405180910390f35b6100fd6102f0565b60405161010a91906106d3565b60405180910390f35b61011b610318565b60405161012891906106d3565b60405180910390f35b61014b60048036038101906101469190610c51565b610340565b60405161015891906106d3565b60405180910390f35b610169610398565b6040516101769190610cf6565b60405180910390f35b61019960048036038101906101949190610e47565b6103b4565b6040516101a691906106d3565b60405180910390f35b6101b7610499565b6040516101c49190610cf6565b60405180910390f35b6101e760048036038101906101e29190610e8e565b6104d2565b6040516101f491906106d3565b60405180910390f35b5f6102066105ce565b82815f01818152505061021881610340565b915050919050565b5f6102296105e0565b8a815f0181905250898160200181815250508881604001905f0b90815f0b8152505087816060019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505086816080019015159081151581525050858160a0019061ffff1916908161ffff191681525050848160c00181905250828161010001819052506102c46105ce565b84815f018181525050808260e001819052506102df826103b4565b925050509998505050505050505050565b6040516020016102ff90610f9b565b6040516020818303038152906040528051906020012081565b60405160200161032790610ff9565b6040516020818303038152906040528051906020012081565b5f60405160200161035090610ff9565b60405160208183030381529060405280519060200120825f015160405160200161037b92919061101c565b604051602081830303815290604052805190602001209050919050565b6040518060a00160405280607e8152602001611322607e913981565b5f6040516020016103c490610f9b565b60405160208183030381529060405280519060200120825f01516040516020016103ee9190611073565b6040516020818303038152906040528051906020012083602001518460400151856060015186608001518760a001518860c0015160405160200161043291906110cd565b604051602081830303815290604052805190602001206104558a60e00151610340565b6104638b61010001516104d2565b60405160200161047c9a9998979695949392919061111f565b604051602081830303815290604052805190602001209050919050565b6040518060400160405280601581526020017f4261722875696e74323536206261725f75696e7429000000000000000000000081525081565b5f80825190505f8167ffffffffffffffff8111156104f3576104f2610704565b5b6040519080825280602002602001820160405280156105215781602001602082028036833780820191505090505b5090505f5b8281101561059d57848181518110610541576105406111b9565b5b60200260200101517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191682828151811061057e5761057d6111b9565b5b602002602001018181525050808061059590611213565b915050610526565b50806040516020016105af919061130b565b6040516020818303038152906040528051906020012092505050919050565b60405180602001604052805f81525090565b604051806101200160405280606081526020015f81526020015f800b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f61ffff191681526020016060815260200161063f6105ce565b8152602001606081525090565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b61066f8161065d565b8114610679575f80fd5b50565b5f8135905061068a81610666565b92915050565b5f602082840312156106a5576106a4610655565b5b5f6106b28482850161067c565b91505092915050565b5f819050919050565b6106cd816106bb565b82525050565b5f6020820190506106e65f8301846106c4565b92915050565b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61073a826106f4565b810181811067ffffffffffffffff8211171561075957610758610704565b5b80604052505050565b5f61076b61064c565b90506107778282610731565b919050565b5f67ffffffffffffffff82111561079657610795610704565b5b61079f826106f4565b9050602081019050919050565b828183375f83830152505050565b5f6107cc6107c78461077c565b610762565b9050828152602081018484840111156107e8576107e76106f0565b5b6107f38482856107ac565b509392505050565b5f82601f83011261080f5761080e6106ec565b5b813561081f8482602086016107ba565b91505092915050565b5f815f0b9050919050565b61083c81610828565b8114610846575f80fd5b50565b5f8135905061085781610833565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6108868261085d565b9050919050565b6108968161087c565b81146108a0575f80fd5b50565b5f813590506108b18161088d565b92915050565b5f8115159050919050565b6108cb816108b7565b81146108d5575f80fd5b50565b5f813590506108e6816108c2565b92915050565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000082169050919050565b610920816108ec565b811461092a575f80fd5b50565b5f8135905061093b81610917565b92915050565b5f67ffffffffffffffff82111561095b5761095a610704565b5b610964826106f4565b9050602081019050919050565b5f61098361097e84610941565b610762565b90508281526020810184848401111561099f5761099e6106f0565b5b6109aa8482856107ac565b509392505050565b5f82601f8301126109c6576109c56106ec565b5b81356109d6848260208601610971565b91505092915050565b5f67ffffffffffffffff8211156109f9576109f8610704565b5b602082029050602081019050919050565b5f80fd5b5f7fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b610a4281610a0e565b8114610a4c575f80fd5b50565b5f81359050610a5d81610a39565b92915050565b5f610a75610a70846109df565b610762565b90508083825260208201905060208402830185811115610a9857610a97610a0a565b5b835b81811015610ac15780610aad8882610a4f565b845260208401935050602081019050610a9a565b5050509392505050565b5f82601f830112610adf57610ade6106ec565b5b8135610aef848260208601610a63565b91505092915050565b5f805f805f805f805f6101208a8c031215610b1657610b15610655565b5b5f8a013567ffffffffffffffff811115610b3357610b32610659565b5b610b3f8c828d016107fb565b9950506020610b508c828d0161067c565b9850506040610b618c828d01610849565b9750506060610b728c828d016108a3565b9650506080610b838c828d016108d8565b95505060a0610b948c828d0161092d565b94505060c08a013567ffffffffffffffff811115610bb557610bb4610659565b5b610bc18c828d016109b2565b93505060e0610bd28c828d0161067c565b9250506101008a013567ffffffffffffffff811115610bf457610bf3610659565b5b610c008c828d01610acb565b9150509295985092959850929598565b5f80fd5b5f80fd5b5f60208284031215610c2d57610c2c610c10565b5b610c376020610762565b90505f610c468482850161067c565b5f8301525092915050565b5f60208284031215610c6657610c65610655565b5b5f610c7384828501610c18565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610cb3578082015181840152602081019050610c98565b5f8484015250505050565b5f610cc882610c7c565b610cd28185610c86565b9350610ce2818560208601610c96565b610ceb816106f4565b840191505092915050565b5f6020820190508181035f830152610d0e8184610cbe565b905092915050565b5f6101208284031215610d2c57610d2b610c10565b5b610d37610120610762565b90505f82013567ffffffffffffffff811115610d5657610d55610c14565b5b610d62848285016107fb565b5f830152506020610d758482850161067c565b6020830152506040610d8984828501610849565b6040830152506060610d9d848285016108a3565b6060830152506080610db1848285016108d8565b60808301525060a0610dc58482850161092d565b60a08301525060c082013567ffffffffffffffff811115610de957610de8610c14565b5b610df5848285016109b2565b60c08301525060e0610e0984828501610c18565b60e08301525061010082013567ffffffffffffffff811115610e2e57610e2d610c14565b5b610e3a84828501610acb565b6101008301525092915050565b5f60208284031215610e5c57610e5b610655565b5b5f82013567ffffffffffffffff811115610e7957610e78610659565b5b610e8584828501610d16565b91505092915050565b5f60208284031215610ea357610ea2610655565b5b5f82013567ffffffffffffffff811115610ec057610ebf610659565b5b610ecc84828501610acb565b91505092915050565b5f81905092915050565b7f466f6f28737472696e6720732c75696e7432353620755f692c696e743820735f5f8201527f692c6164647265737320612c626f6f6c20622c6279746573333020627974657360208201527f5f33302c62797465732064796e5f62797465732c426172206261722c6279746560408201527f73315b5d20617272294261722875696e74323536206261725f75696e74290000606082015250565b5f610f85607e83610ed5565b9150610f9082610edf565b607e82019050919050565b5f610fa582610f79565b9150819050919050565b7f4261722875696e74323536206261725f75696e742900000000000000000000005f82015250565b5f610fe3601583610ed5565b9150610fee82610faf565b601582019050919050565b5f61100382610fd7565b9150819050919050565b6110168161065d565b82525050565b5f60408201905061102f5f8301856106c4565b61103c602083018461100d565b9392505050565b5f61104d82610c7c565b6110578185610ed5565b9350611067818560208601610c96565b80840191505092915050565b5f61107e8284611043565b915081905092915050565b5f81519050919050565b5f81905092915050565b5f6110a782611089565b6110b18185611093565b93506110c1818560208601610c96565b80840191505092915050565b5f6110d8828461109d565b915081905092915050565b6110ec81610828565b82525050565b6110fb8161087c565b82525050565b61110a816108b7565b82525050565b611119816108ec565b82525050565b5f610140820190506111335f83018d6106c4565b611140602083018c6106c4565b61114d604083018b61100d565b61115a606083018a6110e3565b61116760808301896110f2565b61117460a0830188611101565b61118160c0830187611110565b61118e60e08301866106c4565b61119c6101008301856106c4565b6111aa6101208301846106c4565b9b9a5050505050505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61121d8261065d565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361124f5761124e6111e6565b5b600182019050919050565b5f81519050919050565b5f81905092915050565b5f819050602082019050919050565b611286816106bb565b82525050565b5f611297838361127d565b60208301905092915050565b5f602082019050919050565b5f6112b98261125a565b6112c38185611264565b93506112ce8361126e565b805f5b838110156112fe5781516112e5888261128c565b97506112f0836112a3565b9250506001810190506112d1565b5085935050505092915050565b5f61131682846112af565b91508190509291505056fe466f6f28737472696e6720732c75696e7432353620755f692c696e743820735f692c6164647265737320612c626f6f6c20622c627974657333302062797465735f33302c62797465732064796e5f62797465732c426172206261722c6279746573315b5d20617272294261722875696e74323536206261725f75696e7429a2646970667358221220bc6b95913ad2c9956867c11dc3a62d92860e3ca3073e156b238c5e19b1fe556a64736f6c63430008150033 \ No newline at end of file diff --git a/tests/contracts/hash_test_contract.sol b/tests/contracts/hash_test_contract.sol index b020bbd..8ef15d5 100644 --- a/tests/contracts/hash_test_contract.sol +++ b/tests/contracts/hash_test_contract.sol @@ -1,4 +1,5 @@ -pragma solidity >=0.5.0 <0.6.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; pragma experimental ABIEncoderV2; diff --git a/tests/test_chain_parity.py b/tests/test_chain_parity.py index 5022f13..de79008 100644 --- a/tests/test_chain_parity.py +++ b/tests/test_chain_parity.py @@ -22,7 +22,7 @@ def contract(w3): Note this expects the contract to be compiled already. This project's docker-compose config pulls a solc container to do this for you. """ - base_path = 'tests/contracts/build/TestContract' + base_path = 'tests/contracts/TestContract' with open(f'{base_path}.abi', 'r') as f: abi = f.read() with open(f'{base_path}.bin', 'r') as f: diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..43184b1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,22 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py38,py39,py310,py311 +skip_missing_interpreters = true + +[testenv] +install_command = python -m pip install --index-url=http://localhost:9090 --trusted-host=localhost {opts} {packages} +deps = + pytest + twine +setenv = + TINYCSS_SKIP_SPEEDUPS_TESTS = YES + PYTHONPATH = {toxinidir}/src +commands = + pytest --junitxml=junit-{envname}.xml --cov + python3 setup.py sdist +extras = + test \ No newline at end of file