From be2b547d9562d4a20c0d73b7b73bc1fb03cacfdb Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 7 Aug 2019 15:18:30 -0600 Subject: [PATCH 1/2] Use custom codec for eth_abi methods --- conftest.py | 7 + docs/{conventions.rst => abi_types.rst} | 17 +- docs/contracts.rst | 212 ++++++++++++++ docs/examples.rst | 36 +-- docs/index.rst | 2 +- tests/core/contracts/conftest.py | 52 +++- .../contracts/test_contract_call_interface.py | 75 ++++- .../test_contract_constructor_encoding.py | 43 ++- .../contracts/test_contract_deployment.py | 56 +++- .../test_contract_method_abi_encoding.py | 100 ++++++- ...st_contract_method_to_argument_matching.py | 37 +++ .../contracts/test_extracting_event_data.py | 267 +++++++++++++++++- .../test_extracting_event_data_old.py | 4 +- tests/core/filtering/test_utils_functions.py | 16 +- tests/core/shh-module/test_shh_post.py | 34 ++- tests/core/utilities/test_abi_is_encodable.py | 63 ++++- .../test_construct_event_data_set.py | 100 ++++++- .../test_construct_event_filter_params.py | 8 +- .../utilities/test_construct_event_topics.py | 112 +++++++- .../utilities/test_event_filter_builder.py | 8 +- tests/core/utilities/test_validation.py | 2 + web3/_utils/abi.py | 229 ++++++++++++--- web3/_utils/contracts.py | 19 +- web3/_utils/events.py | 32 ++- web3/_utils/filters.py | 22 +- web3/_utils/module_testing/eth_module.py | 7 +- web3/contract.py | 31 +- web3/main.py | 13 + 28 files changed, 1402 insertions(+), 202 deletions(-) rename docs/{conventions.rst => abi_types.rst} (53%) diff --git a/conftest.py b/conftest.py index fc92a10ed4..ba98c6459a 100644 --- a/conftest.py +++ b/conftest.py @@ -102,6 +102,13 @@ def web3(): return Web3(provider) +@pytest.fixture +def web3_strict_types(): + w3 = Web3(EthereumTesterProvider()) + w3.enable_strict_bytes_type_checking() + return w3 + + @pytest.fixture(autouse=True) def print_warnings(): warnings.simplefilter('always') diff --git a/docs/conventions.rst b/docs/abi_types.rst similarity index 53% rename from docs/conventions.rst rename to docs/abi_types.rst index 08c204ae66..03576709ac 100644 --- a/docs/conventions.rst +++ b/docs/abi_types.rst @@ -1,5 +1,5 @@ -Conventions -=========== +ABI Types +========= The Web3 library follows the following conventions. @@ -24,3 +24,16 @@ All addresses must be supplied in one of three ways: * A 20-byte hexadecimal that is checksummed using the `EIP-55 `_ spec. * A 20-byte binary address. + +Strict Bytes Type Checking +-------------------------- + +There is a method on web3 that will enable stricter bytes type checking. +The default is to allow Python strings, and to allow bytestrings less +than the specified byte size. To enable stricter checks, use +``w3.enable_strict_bytes_type_checking()``. This method will cause the web3 +instance to raise an error if a Python string is passed in without a "0x" +prefix. It will also raise an error if the byte string or hex string is not +the exact number of bytes specified by the ABI type. See the +:ref:`enable-strict-byte-check` section +for an example and more details. diff --git a/docs/contracts.rst b/docs/contracts.rst index 0b6bd1e7b5..1efc48cac8 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -491,6 +491,218 @@ and the arguments are ambiguous. 1 +.. _enable-strict-byte-check: + +Enabling Strict Checks for Bytes Types +-------------------------------------- + +By default, web3 is not very strict when it comes to hex and bytes values. +A bytes type will take a hex string, a bytestring, or a regular python +string that can be decoded as a hex. +Additionally, if an abi specifies a byte size, but the value that gets +passed in is less than the specified size, web3 will automatically pad the value. +For example, if an abi specifies a type of ``bytes4``, web3 will handle all of the following values: + +.. list-table:: Valid byte and hex strings for a bytes4 type + :widths: 25 75 + :header-rows: 1 + + * - Input + - Normalizes to + * - ``''`` + - ``b'\x00\x00\x00\x00'`` + * - ``'0x'`` + - ``b'\x00\x00\x00\x00'`` + * - ``b''`` + - ``b'\x00\x00\x00\x00'`` + * - ``b'ab'`` + - ``b'ab\x00\x00'`` + * - ``'0xab'`` + - ``b'\xab\x00\x00\x00'`` + * - ``'1234'`` + - ``b'\x124\x00\x00'`` + * - ``'0x61626364'`` + - ``b'abcd'`` + * - ``'1234'`` + - ``b'1234'`` + + +The following values will raise an error by default: + +.. list-table:: Invalid byte and hex strings for a bytes4 type + :widths: 25 75 + :header-rows: 1 + + * - Input + - Reason + * - ``b'abcde'`` + - Bytestring with more than 4 bytes + * - ``'0x6162636423'`` + - Hex string with more than 4 bytes + * - ``2`` + - Wrong type + * - ``'ah'`` + - String is not valid hex + +However, you may want to be stricter with acceptable values for bytes types. +For this you can use the ``enable_strict_bytes_type_checking`` method, +which is available on the web3 instance. A web3 instance which has had this method +invoked will enforce a stricter set of rules on which values are accepted. + + - A Python string that is not prefixed with ``0x`` will throw an error. + - A bytestring that is less than (or greater than) the specified byte size + will raise an error. + +.. list-table:: Valid byte and hex strings for a strict bytes4 type + :widths: 25 75 + :header-rows: 1 + + * - Input + - Normalizes to + * - ``'0x'`` + - ``b'\x00\x00\x00\x00'`` + * - ``'0x61626364'`` + - ``b'abcd'`` + * - ``'1234'`` + - ``b'1234'`` + +.. list-table:: Invalid byte and hex strings with strict bytes4 type checking + :widths: 25 75 + :header-rows: 1 + + * - Input + - Reason + * - ``''`` + - Needs to be prefixed with a "0x" to be interpreted as an empty hex string + * - ``'1234'`` + - Needs to either be a bytestring (b'1234') or be a hex value of the right size, prefixed with 0x (in this case: '0x31323334') + * - ``b''`` + - Needs to have exactly 4 bytes + * - ``b'ab'`` + - Needs to have exactly 4 bytes + * - ``'0xab'`` + - Needs to have exactly 4 bytes + * - ``'0x6162636464'`` + - Needs to have exactly 4 bytes + + +For example, the following contract code will generate the abi below and some bytecode: + +.. testsetup:: + + from web3 import Web3 + w3 = Web3(Web3.EthereumTesterProvider()) + bytecode = "608060405234801561001057600080fd5b506040516106103803806106108339810180604052602081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185602082028301116401000000008211171561007e57600080fd5b5050929190505050806000908051906020019061009c9291906100a3565b505061019c565b82805482825590600052602060002090600f0160109004810192821561015a5791602002820160005b8382111561012a57835183826101000a81548161ffff02191690837e010000000000000000000000000000000000000000000000000000000000009004021790555092602001926002016020816001010492830192600103026100cc565b80156101585782816101000a81549061ffff021916905560020160208160010104928301926001030261012a565b505b509050610167919061016b565b5090565b61019991905b8082111561019557600081816101000a81549061ffff021916905550600101610171565b5090565b90565b610465806101ab6000396000f3fe608060405260043610610051576000357c0100000000000000000000000000000000000000000000000000000000900480633b3230ee14610056578063d7c8a410146100e7578063dfe3136814610153575b600080fd5b34801561006257600080fd5b5061008f6004803603602081101561007957600080fd5b8101908080359060200190929190505050610218565b60405180827dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b3480156100f357600080fd5b506100fc61026c565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561013f578082015181840152602081019050610124565b505050509050019250505060405180910390f35b34801561015f57600080fd5b506102166004803603602081101561017657600080fd5b810190808035906020019064010000000081111561019357600080fd5b8201836020820111156101a557600080fd5b803590602001918460208302840111640100000000831117156101c757600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050610326565b005b60008181548110151561022757fe5b9060005260206000209060109182820401919006600202915054906101000a90047e010000000000000000000000000000000000000000000000000000000000000281565b6060600080548060200260200160405190810160405280929190818152602001828054801561031c57602002820191906000526020600020906000905b82829054906101000a90047e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190600201906020826001010492830192600103820291508084116102a95790505b5050505050905090565b806000908051906020019061033c929190610340565b5050565b82805482825590600052602060002090600f016010900481019282156103f75791602002820160005b838211156103c757835183826101000a81548161ffff02191690837e01000000000000000000000000000000000000000000000000000000000000900402179055509260200192600201602081600101049283019260010302610369565b80156103f55782816101000a81549061ffff02191690556002016020816001010492830192600103026103c7565b505b5090506104049190610408565b5090565b61043691905b8082111561043257600081816101000a81549061ffff02191690555060010161040e565b5090565b9056fea165627a7a72305820a8f9f1f4815c1eedfb8df31298a5cd13b198895de878871328b5d96296b69b4e0029" + +.. code-block:: + + >>> # pragma solidity >=0.4.22 <0.6.0; + ... + ... # contract ArraysContract { + ... # bytes2[] public bytes2Value; + + ... # constructor(bytes2[] memory _bytes2Value) public { + ... # bytes2Value = _bytes2Value; + ... # } + + ... # function setBytes2Value(bytes2[] memory _bytes2Value) public { + ... # bytes2Value = _bytes2Value; + ... # } + + ... # function getBytes2Value() public view returns (bytes2[] memory) { + ... # return bytes2Value; + ... # } + ... # } + +.. doctest:: + + >>> abi = ''' + ... [ + ... { + ... "constant": true, + ... "inputs": [ + ... { + ... "name": "", + ... "type": "uint256" + ... } + ... ], + ... "name": "bytes2Value", + ... "outputs": [ + ... { + ... "name": "", + ... "type": "bytes2" + ... } + ... ], + ... "payable": false, + ... "stateMutability": "view", + ... "type": "function" + ... }, + ... { + ... "constant": true, + ... "inputs": [], + ... "name": "getBytes2Value", + ... "outputs": [ + ... { + ... "name": "", + ... "type": "bytes2[]" + ... } + ... ], + ... "payable": false, + ... "stateMutability": "view", + ... "type": "function" + ... }, + ... { + ... "constant": false, + ... "inputs": [ + ... { + ... "name": "_bytes2Value", + ... "type": "bytes2[]" + ... } + ... ], + ... "name": "setBytes2Value", + ... "outputs": [], + ... "payable": false, + ... "stateMutability": "nonpayable", + ... "type": "function" + ... }, + ... { + ... "inputs": [ + ... { + ... "name": "_bytes2Value", + ... "type": "bytes2[]" + ... } + ... ], + ... "payable": false, + ... "stateMutability": "nonpayable", + ... "type": "constructor" + ... } + ... ] + ... '''.strip() + >>> # bytecode = "6080..." + + >>> ArraysContract = w3.eth.contract(abi=abi, bytecode=bytecode) + + >>> tx_hash = ArraysContract.constructor([b'b']).transact() + >>> tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) + + >>> array_contract = w3.eth.contract( + ... address=tx_receipt.contractAddress, + ... abi=abi + ... ) + + >>> array_contract.functions.getBytes2Value().call() + [b'b\x00'] + >>> array_contract.functions.setBytes2Value([b'a']).transact() + HexBytes('0x39bc9a0bf5b8ec8e8115ccb20bf02f5570351a20a8fd774da91353f38535bec1') + >>> array_contract.functions.getBytes2Value().call() + [b'a\x00'] + >>> w3.enable_strict_bytes_type_checking() + >>> array_contract.functions.setBytes2Value([b'a']).transact() + Traceback (most recent call last): + ... + ValidationError: + Could not identify the intended function with name `setBytes2Value` + Contract Functions ------------------ diff --git a/docs/examples.rst b/docs/examples.rst index dde92106ca..3cc2fde727 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -202,19 +202,19 @@ Given the following solidity source file stored at ``contract.sol``. .. code-block:: javascript contract StoreVar { - + uint8 public _myVar; event MyEvent(uint indexed _var); - + function setVar(uint8 _var) public { _myVar = _var; MyEvent(_var); } - + function getVar() public view returns (uint8) { return _myVar; } - + } The following example demonstrates a few things: @@ -229,45 +229,45 @@ The following example demonstrates a few things: import sys import time import pprint - + from web3.providers.eth_tester import EthereumTesterProvider from web3 import Web3 from solc import compile_source - + def compile_source_file(file_path): with open(file_path, 'r') as f: source = f.read() - + return compile_source(source) - - + + def deploy_contract(w3, contract_interface): tx_hash = w3.eth.contract( abi=contract_interface['abi'], bytecode=contract_interface['bin']).deploy() - + address = w3.eth.getTransactionReceipt(tx_hash)['contractAddress'] return address - - + + w3 = Web3(EthereumTesterProvider()) - + contract_source_path = 'contract.sol' compiled_sol = compile_source_file('contract.sol') - + contract_id, contract_interface = compiled_sol.popitem() - + address = deploy_contract(w3, contract_interface) print("Deployed {0} to: {1}\n".format(contract_id, address)) - + store_var_contract = w3.eth.contract( address=address, abi=contract_interface['abi']) - + gas_estimate = store_var_contract.functions.setVar(255).estimateGas() print("Gas estimate to transact with setVar: {0}\n".format(gas_estimate)) - + if gas_estimate < 100000: print("Sending transaction to setVar(255)\n") tx_hash = store_var_contract.functions.setVar(255).transact() diff --git a/docs/index.rst b/docs/index.rst index 73f54c8afa..70fb31cd7e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,7 @@ Contents ens ethpm internals - conventions + abi_types releases Indices and tables diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 01b02ee479..36617113f1 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -6,6 +6,11 @@ event_signature_to_log_topic, ) +from web3._utils.module_testing.emitter_contract import ( + CONTRACT_EMITTER_ABI, + CONTRACT_EMITTER_CODE, + CONTRACT_EMITTER_RUNTIME, +) from web3._utils.module_testing.event_contract import ( EVNT_CONTRACT_ABI, EVNT_CONTRACT_CODE, @@ -209,6 +214,18 @@ def WithConstructorArgumentsContract(web3, ) +@pytest.fixture() +def WithConstructorArgumentsContractStrict(web3_strict_types, + WITH_CONSTRUCTOR_ARGUMENTS_CODE, + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + WITH_CONSTRUCTOR_ARGUMENTS_ABI): + return web3_strict_types.eth.contract( + abi=WITH_CONSTRUCTOR_ARGUMENTS_ABI, + bytecode=WITH_CONSTRUCTOR_ARGUMENTS_CODE, + bytecode_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, + ) + + CONTRACT_WITH_CONSTRUCTOR_ADDRESS_CODE = "0x6060604052604051602080607683395060806040525160008054600160a060020a031916821790555060428060346000396000f3606060405260e060020a600035046334664e3a8114601a575b005b603860005473ffffffffffffffffffffffffffffffffffffffff1681565b6060908152602090f3" # noqa: E501 CONTRACT_WITH_CONSTRUCTOR_ADDRESS_RUNTIME = "0x606060405260e060020a600035046334664e3a8114601a575b005b603860005473ffffffffffffffffffffffffffffffffffffffff1681565b6060908152602090f3" # noqa: E501 CONTRACT_WITH_CONSTRUCTOR_ADDRESS_ABI = json.loads('[{"constant":true,"inputs":[],"name":"testAddr","outputs":[{"name":"","type":"address"}],"type":"function"},{"inputs":[{"name":"_testAddr","type":"address"}],"type":"constructor"}]') # noqa: E501 @@ -358,13 +375,6 @@ def Bytes32Contract(web3, BYTES32_CONTRACT): return web3.eth.contract(**BYTES32_CONTRACT) -CONTRACT_EMITTER_CODE = "6060604052341561000f57600080fd5b6107928061001e6000396000f300606060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806317c0c1801461007d57806320f0256e146100a357806390b41d8b146100ed5780639c37705314610125578063aa6fd82214610166578063acabb9ed14610195575b600080fd5b341561008857600080fd5b6100a1600480803560ff16906020019091905050610235565b005b34156100ae57600080fd5b6100eb600480803560ff169060200190919080359060200190919080359060200190919080359060200190919080359060200190919050506102bd565b005b34156100f857600080fd5b610123600480803560ff169060200190919080359060200190919080359060200190919050506103a2565b005b341561013057600080fd5b610164600480803560ff169060200190919080359060200190919080359060200190919080359060200190919050506104a8565b005b341561017157600080fd5b610193600480803560ff1690602001909190803590602001909190505061057c565b005b34156101a057600080fd5b610233600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610663565b005b6001600e81111561024257fe5b81600e81111561024e57fe5b1415610285577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60405160405180910390a16102ba565b6000600e81111561029257fe5b81600e81111561029e57fe5b14156102b45760405160405180910390a06102b9565b600080fd5b5b50565b6005600e8111156102ca57fe5b85600e8111156102d657fe5b1415610330577ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf9848484846040518085815260200184815260200183815260200182815260200194505050505060405180910390a161039b565b600b600e81111561033d57fe5b85600e81111561034957fe5b14156103955780827fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b58686604051808381526020018281526020019250505060405180910390a361039a565b600080fd5b5b5050505050565b6003600e8111156103af57fe5b83600e8111156103bb57fe5b1415610405577fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc58282604051808381526020018281526020019250505060405180910390a16104a3565b6009600e81111561041257fe5b83600e81111561041e57fe5b141561046157807f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca836040518082815260200191505060405180910390a26104a2565b6008600e81111561046e57fe5b83600e81111561047a57fe5b141561049c5780826040518082815260200191505060405180910390a16104a1565b600080fd5b5b5b505050565b6004600e8111156104b557fe5b84600e8111156104c157fe5b1415610513577f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f5506283838360405180848152602001838152602001828152602001935050505060405180910390a1610576565b600a600e81111561052057fe5b84600e81111561052c57fe5b14156105705780827ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec856040518082815260200191505060405180910390a3610575565b600080fd5b5b50505050565b6002600e81111561058957fe5b82600e81111561059557fe5b14156105d7577f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d4816040518082815260200191505060405180910390a161065f565b6007600e8111156105e457fe5b82600e8111156105f057fe5b141561062857807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560405160405180910390a261065e565b6006600e81111561063557fe5b82600e81111561064157fe5b1415610658578060405160405180910390a161065d565b600080fd5b5b5b5050565b816040518082805190602001908083835b6020831015156106995780518252602082019150602081019050602083039250610674565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207fe77cf33df73da7bc2e253a2dae617e6f15e4e337eaa462a108903af4643d1b75826040518080602001828103825283818151815260200191508051906020019080838360005b8381101561072857808201518184015260208101905061070d565b50505050905090810190601f1680156107555780820380516001836020036101000a031916815260200191505b509250505060405180910390a250505600a165627a7a723058209c8a4fb2bf8b853d1fb9ec7399bd5b69cab6b7fb351c1a81c39edcf6573180fb0029" # noqa: E501 - -CONTRACT_EMITTER_RUNTIME = "606060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806317c0c1801461007d57806320f0256e146100a357806390b41d8b146100ed5780639c37705314610125578063aa6fd82214610166578063acabb9ed14610195575b600080fd5b341561008857600080fd5b6100a1600480803560ff16906020019091905050610235565b005b34156100ae57600080fd5b6100eb600480803560ff169060200190919080359060200190919080359060200190919080359060200190919080359060200190919050506102bd565b005b34156100f857600080fd5b610123600480803560ff169060200190919080359060200190919080359060200190919050506103a2565b005b341561013057600080fd5b610164600480803560ff169060200190919080359060200190919080359060200190919080359060200190919050506104a8565b005b341561017157600080fd5b610193600480803560ff1690602001909190803590602001909190505061057c565b005b34156101a057600080fd5b610233600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610663565b005b6001600e81111561024257fe5b81600e81111561024e57fe5b1415610285577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60405160405180910390a16102ba565b6000600e81111561029257fe5b81600e81111561029e57fe5b14156102b45760405160405180910390a06102b9565b600080fd5b5b50565b6005600e8111156102ca57fe5b85600e8111156102d657fe5b1415610330577ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf9848484846040518085815260200184815260200183815260200182815260200194505050505060405180910390a161039b565b600b600e81111561033d57fe5b85600e81111561034957fe5b14156103955780827fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b58686604051808381526020018281526020019250505060405180910390a361039a565b600080fd5b5b5050505050565b6003600e8111156103af57fe5b83600e8111156103bb57fe5b1415610405577fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc58282604051808381526020018281526020019250505060405180910390a16104a3565b6009600e81111561041257fe5b83600e81111561041e57fe5b141561046157807f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca836040518082815260200191505060405180910390a26104a2565b6008600e81111561046e57fe5b83600e81111561047a57fe5b141561049c5780826040518082815260200191505060405180910390a16104a1565b600080fd5b5b5b505050565b6004600e8111156104b557fe5b84600e8111156104c157fe5b1415610513577f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f5506283838360405180848152602001838152602001828152602001935050505060405180910390a1610576565b600a600e81111561052057fe5b84600e81111561052c57fe5b14156105705780827ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec856040518082815260200191505060405180910390a3610575565b600080fd5b5b50505050565b6002600e81111561058957fe5b82600e81111561059557fe5b14156105d7577f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d4816040518082815260200191505060405180910390a161065f565b6007600e8111156105e457fe5b82600e8111156105f057fe5b141561062857807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560405160405180910390a261065e565b6006600e81111561063557fe5b82600e81111561064157fe5b1415610658578060405160405180910390a161065d565b600080fd5b5b5b5050565b816040518082805190602001908083835b6020831015156106995780518252602082019150602081019050602083039250610674565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207fe77cf33df73da7bc2e253a2dae617e6f15e4e337eaa462a108903af4643d1b75826040518080602001828103825283818151815260200191508051906020019080838360005b8381101561072857808201518184015260208101905061070d565b50505050905090810190601f1680156107555780820380516001836020036101000a031916815260200191505b509250505060405180910390a250505600a165627a7a723058209c8a4fb2bf8b853d1fb9ec7399bd5b69cab6b7fb351c1a81c39edcf6573180fb0029" # noqa: E501 - -CONTRACT_EMITTER_ABI = json.loads('[{"constant":false,"inputs":[{"name":"v","type":"string"}],"name":"logString","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"}],"name":"logNoArgs","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"},{"name":"arg3","type":"uint256"}],"name":"logQuadruple","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"}],"name":"logDouble","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"}],"name":"logTriple","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"}],"name":"logSingle","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"arg0","type":"string"},{"name":"arg1","type":"string"}],"name":"logDynamicArgs","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"v","type":"bytes"}],"name":"logBytes","outputs":[],"payable":false,"type":"function"},{"anonymous":true,"inputs":[],"name":"LogAnonymous","type":"event"},{"anonymous":false,"inputs":[],"name":"LogNoArguments","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"}],"name":"LogSingleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"}],"name":"LogDoubleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"}],"name":"LogTripleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"},{"indexed":false,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleArg","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"name":"arg0","type":"uint256"}],"name":"LogSingleAnonymous","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"arg0","type":"uint256"}],"name":"LogSingleWithIndex","type":"event"},{"anonymous":true,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"}],"name":"LogDoubleAnonymous","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"}],"name":"LogDoubleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"}],"name":"LogTripleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"},{"indexed":true,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"arg0","type":"string"},{"indexed":false,"name":"arg1","type":"string"}],"name":"LogDynamicArgs","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"v","type":"bytes"}],"name":"LogBytes","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"v","type":"string"}],"name":"LogString","type":"event"}]') # noqa: E501 - - @pytest.fixture() def EMITTER_CODE(): return CONTRACT_EMITTER_CODE @@ -392,23 +402,29 @@ def EMITTER(EMITTER_CODE, @pytest.fixture() -def Emitter(web3_empty, EMITTER): - web3 = web3_empty +def StrictEmitter(web3_strict_types, EMITTER): + web3 = web3_strict_types return web3.eth.contract(**EMITTER) @pytest.fixture() -def emitter(web3_empty, Emitter, wait_for_transaction, wait_for_block, address_conversion_func): - web3 = web3_empty +def strict_emitter(web3_strict_types, + StrictEmitter, + wait_for_transaction, + wait_for_block, + address_conversion_func): + web3 = web3_strict_types wait_for_block(web3) - deploy_txn_hash = Emitter.constructor().transact({'from': web3.eth.coinbase, 'gas': 1000000}) + deploy_txn_hash = StrictEmitter.constructor().transact( + {'from': web3.eth.coinbase, 'gas': 1000000} + ) deploy_receipt = wait_for_transaction(web3, deploy_txn_hash) contract_address = address_conversion_func(deploy_receipt['contractAddress']) bytecode = web3.eth.getCode(contract_address) - assert bytecode == Emitter.bytecode_runtime - emitter_contract = Emitter(address=contract_address) + assert bytecode == StrictEmitter.bytecode_runtime + emitter_contract = StrictEmitter(address=contract_address) assert emitter_contract.address == contract_address return emitter_contract @@ -608,6 +624,11 @@ def ArraysContract(web3, ARRAYS_CONTRACT): return web3.eth.contract(**ARRAYS_CONTRACT) +@pytest.fixture() +def StrictArraysContract(web3_strict_types, ARRAYS_CONTRACT): + return web3_strict_types.eth.contract(**ARRAYS_CONTRACT) + + CONTRACT_PAYABLE_TESTER_SOURCE = """ contract PayableTester { bool public wasCalled; @@ -854,6 +875,9 @@ class LogTopics: LogBytes = _encode_to_topic("LogBytes(bytes)") LogString = _encode_to_topic("LogString(string)") LogDynamicArgs = _encode_to_topic("LogDynamicArgs(string,string)") + LogListArgs = _encode_to_topic("LogListArgs(bytes2[],bytes2[])") + LogAddressIndexed = _encode_to_topic("LogAddressIndexed(address,address)") + LogAddressNotIndexed = _encode_to_topic("LogAddressNotIndexed(address,address)") @pytest.fixture() diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index af9269513c..f17ff8851e 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -9,6 +9,9 @@ import pytest import eth_abi +from eth_utils import ( + is_text, +) from eth_utils.toolz import ( identity, ) @@ -71,6 +74,22 @@ def arrays_contract(web3, ArraysContract, address_conversion_func): return deploy(web3, ArraysContract, address_conversion_func, args=[bytes32_array, byte_arr]) +@pytest.fixture() +def strict_arrays_contract(web3_strict_types, StrictArraysContract, address_conversion_func): + # bytes_32 = [keccak('0'), keccak('1')] + bytes32_array = [ + b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 + b'\xc8\x9e\xfd\xaaT\xc0\xf2\x0cz\xdfa(\x82\xdf\tP\xf5\xa9Qc~\x03\x07\xcd\xcbLg/)\x8b\x8b\xc6', # noqa: E501 + ] + byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] + return deploy( + web3_strict_types, + StrictArraysContract, + address_conversion_func, + args=[bytes32_array, byte_arr] + ) + + @pytest.fixture() def address_contract(web3, WithConstructorAddressArgumentsContract, address_conversion_func): return deploy( @@ -83,7 +102,14 @@ def address_contract(web3, WithConstructorAddressArgumentsContract, address_conv @pytest.fixture(params=[b'\x04\x06', '0x0406', '0406']) def bytes_contract(web3, BytesContract, request, address_conversion_func): - return deploy(web3, BytesContract, address_conversion_func, args=[request.param]) + if is_text(request.param) and request.param[:2] != '0x': + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + return deploy(web3, BytesContract, address_conversion_func, args=[request.param]) + else: + return deploy(web3, BytesContract, address_conversion_func, args=[request.param]) @pytest.fixture() @@ -110,7 +136,11 @@ def call_transaction(): HexBytes('0406040604060406040604060406040604060406040604060406040604060406'), ]) def bytes32_contract(web3, Bytes32Contract, request, address_conversion_func): - return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) + if is_text(request.param) and request.param[:2] != '0x': + with pytest.warns(DeprecationWarning): + return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) + else: + return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) @pytest.fixture() @@ -252,6 +282,47 @@ def test_call_get_byte_array(arrays_contract, call): assert result == expected_byte_arr +@pytest.mark.parametrize('args,expected', [([b''], [b'\x00']), (['0x'], [b'\x00'])]) +def test_set_byte_array(arrays_contract, call, transact, args, expected): + transact( + contract=arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + result = call(contract=arrays_contract, + contract_function='getByteValue') + + assert result == expected + + +@pytest.mark.parametrize( + 'args,expected', [ + ([b'1'], [b'1']), + (['0xDe'], [b'\xDe']) + ] +) +def test_set_strict_byte_array(strict_arrays_contract, call, transact, args, expected): + transact( + contract=strict_arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + result = call(contract=strict_arrays_contract, + contract_function='getByteValue') + + assert result == expected + + +@pytest.mark.parametrize('args', ([''], ['s'])) +def test_set_strict_byte_array_with_invalid_args(strict_arrays_contract, transact, args): + with pytest.raises(ValidationError): + transact( + contract=strict_arrays_contract, + contract_function='setByteValue', + func_args=[args] + ) + + def test_call_get_byte_const_array(arrays_contract, call): result = call(contract=arrays_contract, contract_function='getByteConstValue') diff --git a/tests/core/contracts/test_contract_constructor_encoding.py b/tests/core/contracts/test_contract_constructor_encoding.py index 10e9861e23..8ed8f264e5 100644 --- a/tests/core/contracts/test_contract_constructor_encoding.py +++ b/tests/core/contracts/test_contract_constructor_encoding.py @@ -1,8 +1,5 @@ import pytest -from eth_abi import ( - encode_abi, -) from eth_utils import ( encode_hex, remove_0x_prefix, @@ -53,13 +50,47 @@ def test_error_if_invalid_arguments_supplied(WithConstructorArgumentsContract, a 'bytes_arg', ( b'abcd', - '61626364', '0x61626364', ), ) -def test_contract_constructor_encoding_encoding(WithConstructorArgumentsContract, bytes_arg): +def test_contract_constructor_encoding_encoding(web3, WithConstructorArgumentsContract, bytes_arg): deploy_data = WithConstructorArgumentsContract._encode_constructor_data([1234, bytes_arg]) encoded_args = '0x00000000000000000000000000000000000000000000000000000000000004d26162636400000000000000000000000000000000000000000000000000000000' # noqa: E501 - expected_ending = encode_hex(encode_abi(['uint256', 'bytes32'], [1234, b'abcd'])) + expected_ending = encode_hex(web3.codec.encode_abi(['uint256', 'bytes32'], [1234, b'abcd'])) assert expected_ending == encoded_args assert deploy_data.endswith(remove_0x_prefix(expected_ending)) + + +def test_contract_constructor_encoding_encoding_warning(web3, WithConstructorArgumentsContract): + with pytest.warns( + DeprecationWarning, + match="in v6 it will be invalid to pass a hex string without the \"0x\" prefix" + ): + deploy_data = WithConstructorArgumentsContract._encode_constructor_data([1234, '61626364']) + encoded_args = '0x00000000000000000000000000000000000000000000000000000000000004d26162636400000000000000000000000000000000000000000000000000000000' # noqa: E501 + + expected_ending = encode_hex( + web3.codec.encode_abi(['uint256', 'bytes32'], [1234, b'abcd']) + ) + assert expected_ending == encoded_args + assert deploy_data.endswith(remove_0x_prefix(expected_ending)) + + +@pytest.mark.parametrize( + 'bytes_arg', + ( + b'abcd', + '0x61626364', + '', + '61626364', + ), +) +def test_contract_constructor_encoding_encoding_strict( + web3_strict_types, + WithConstructorArgumentsContractStrict, + bytes_arg): + with pytest.raises( + TypeError, + match="One or more arguments could not be encoded to the necessary ABI type." + ): + WithConstructorArgumentsContractStrict._encode_constructor_data([1234, bytes_arg]) diff --git a/tests/core/contracts/test_contract_deployment.py b/tests/core/contracts/test_contract_deployment.py index 88cf8b2817..dbc62f2ab7 100644 --- a/tests/core/contracts/test_contract_deployment.py +++ b/tests/core/contracts/test_contract_deployment.py @@ -4,9 +4,9 @@ from eth_utils import ( decode_hex, ) - -# Ignore warning in pyethereum 1.6 - will go away with the upgrade -pytestmark = pytest.mark.filterwarnings("ignore:implicit cast from 'char *'") +from web3.exceptions import ( + ValidationError, +) def test_contract_deployment_no_constructor(web3, MathContract, @@ -41,18 +41,54 @@ def test_contract_deployment_with_constructor_without_args(web3, def test_contract_deployment_with_constructor_with_arguments(web3, WithConstructorArgumentsContract, WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME): - deploy_txn = WithConstructorArgumentsContract.constructor(1234, 'abcd').transact() + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + deploy_txn = WithConstructorArgumentsContract.constructor(1234, 'abcd').transact() - txn_receipt = web3.eth.waitForTransactionReceipt(deploy_txn) - assert txn_receipt is not None + txn_receipt = web3.eth.waitForTransactionReceipt(deploy_txn) + assert txn_receipt is not None - assert txn_receipt['contractAddress'] - contract_address = txn_receipt['contractAddress'] + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] - blockchain_code = web3.eth.getCode(contract_address) - assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) + blockchain_code = web3.eth.getCode(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) +@pytest.mark.parametrize('constructor_arg', ( + b'1234\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', # noqa: E501 + '0x0000000000000000000000000000000000000000000000000000000000000000' + ) +) +def test_contract_deployment_with_constructor_with_arguments_strict(web3_strict_types, + WithConstructorArgumentsContractStrict, # noqa: E501 + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, # noqa: E501 + constructor_arg): + deploy_txn = WithConstructorArgumentsContractStrict.constructor( + 1234, constructor_arg + ).transact() + + txn_receipt = web3_strict_types.eth.waitForTransactionReceipt(deploy_txn) + assert txn_receipt is not None + + assert txn_receipt['contractAddress'] + contract_address = txn_receipt['contractAddress'] + + blockchain_code = web3_strict_types.eth.getCode(contract_address) + assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) + + +def test_contract_deployment_with_constructor_with_arguments_strict_error(web3_strict_types, + WithConstructorArgumentsContractStrict, # noqa: E501 + WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME): # noqa: E501 + with pytest.raises( + TypeError, + match="One or more arguments could not be encoded to the necessary ABI type. Expected types are: uint256, bytes32" # noqa: E501 + ): + WithConstructorArgumentsContractStrict.constructor(1234, 'abcd').transact() + def test_contract_deployment_with_constructor_with_address_argument(web3, WithConstructorAddressArgumentsContract, # noqa: E501 WITH_CONSTRUCTOR_ADDRESS_RUNTIME): # noqa: E501 diff --git a/tests/core/contracts/test_contract_method_abi_encoding.py b/tests/core/contracts/test_contract_method_abi_encoding.py index 463e569d2e..1e2772e95d 100644 --- a/tests/core/contracts/test_contract_method_abi_encoding.py +++ b/tests/core/contracts/test_contract_method_abi_encoding.py @@ -1,6 +1,10 @@ import json import pytest +from web3.exceptions import ( + ValidationError, +) + ABI_A = json.loads('[{"constant":false,"inputs":[],"name":"a","outputs":[],"type":"function"}]') ABI_B = json.loads('[{"constant":false,"inputs":[{"name":"","type":"uint256"}],"name":"a","outputs":[],"type":"function"}]') # noqa: E501 ABI_C = json.loads('[{"constant":false,"inputs":[],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"a","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"","type":"uint256"}],"name":"a","outputs":[],"type":"function"}]') # noqa: E501 @@ -47,13 +51,6 @@ None, '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000', ), - ( - ABI_C, - 'a', - ['61'], - None, - '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000', - ), ), ) def test_contract_abi_encoding(web3, abi, method, arguments, data, expected): @@ -62,6 +59,18 @@ def test_contract_abi_encoding(web3, abi, method, arguments, data, expected): assert actual == expected +def test_contract_abi_encoding_warning(web3): + contract = web3.eth.contract(abi=ABI_C) + + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + + actual = contract.encodeABI('a', ['61'], data=None) + assert actual == '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000' # noqa: E501 + + @pytest.mark.parametrize( 'abi,method,kwargs,expected', ( @@ -82,3 +91,80 @@ def test_contract_abi_encoding_kwargs(web3, abi, method, kwargs, expected): contract = web3.eth.contract(abi=abi) actual = contract.encodeABI(method, kwargs=kwargs) assert actual == expected + + +@pytest.mark.parametrize( + 'abi,method,arguments,data', + ( + ( + ABI_C, + 'a', + [b'a'], + None, + ), + ( + ABI_C, + 'a', + ['0x61'], + None, + ), + ( + ABI_C, + 'a', + ['61'], + None, + ), + ), +) +def test_contract_abi_encoding_strict_with_error(web3_strict_types, abi, method, arguments, data): + contract = web3_strict_types.eth.contract(abi=abi) + with pytest.raises(ValidationError): + contract.encodeABI(method, arguments, data=data) + + +@pytest.mark.parametrize( + 'abi,method,arguments,data,expected', + ( + (ABI_A, 'a', [], None, '0x0dbe671f'), + (ABI_A, 'a', [], '0x12345678', '0x12345678'), + ( + ABI_B, + 'a', + [0], + None, + '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ABI_B, + 'a', + [1], + None, + '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ABI_C, + 'a', + [1], + None, + '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ABI_C, + 'a', + [b'00000000000000000000000000000000'], + None, + '0x9f3fab583030303030303030303030303030303030303030303030303030303030303030', + ), + ( + ABI_C, + 'a', + ['0x0000000000000000000000000000000000000000000000000000000000000000'], + None, + '0x9f3fab580000000000000000000000000000000000000000000000000000000000000000', + ), + ), +) +def test_contract_abi_encoding_strict(web3_strict_types, abi, method, arguments, data, expected): + contract = web3_strict_types.eth.contract(abi=abi) + actual = contract.encodeABI(method, arguments, data=data) + assert actual == expected diff --git a/tests/core/contracts/test_contract_method_to_argument_matching.py b/tests/core/contracts/test_contract_method_to_argument_matching.py index d8ef9be4bc..d0e2325f6c 100644 --- a/tests/core/contracts/test_contract_method_to_argument_matching.py +++ b/tests/core/contracts/test_contract_method_to_argument_matching.py @@ -128,6 +128,7 @@ def test_error_when_no_function_name_match(web3): ([], []), ([b'arst'], ['bytes32']), (['0xf00b47'], ['bytes32']), + (['0x'], ['bytes32']), ([1234567890], ['uint256']), # ([255], ['uint8']), # TODO: enable ([-1], ['int8']), @@ -143,8 +144,44 @@ def test_finds_function_with_matching_args(web3, arguments, expected_types): assert set(get_abi_input_types(abi)) == set(expected_types) +def test_finds_function_with_matching_args_deprecation_warning(web3): + Contract = web3.eth.contract(abi=MULTIPLE_FUNCTIONS) + + with pytest.warns(DeprecationWarning): + abi = Contract._find_matching_fn_abi('a', ['']) + assert abi['name'] == 'a' + assert len(abi['inputs']) == len(['bytes32']) + assert set(get_abi_input_types(abi)) == set(['bytes32']) + + def test_error_when_duplicate_match(web3): Contract = web3.eth.contract(abi=MULTIPLE_FUNCTIONS) with pytest.raises(ValidationError): Contract._find_matching_fn_abi('a', [100]) + + +@pytest.mark.parametrize('arguments', (['0xf00b47'], [b''], [''], ['00' * 16])) +def test_strict_errors_if_type_is_wrong(web3_strict_types, arguments): + Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS) + + with pytest.raises(ValidationError): + Contract._find_matching_fn_abi('a', arguments) + + +@pytest.mark.parametrize( + 'arguments,expected_types', + ( + ([], []), + ([1234567890], ['uint256']), + ([-1], ['int8']), + ([[(-1, True), (2, False)]], ['(int256,bool)[]']), + ) +) +def test_strict_finds_function_with_matching_args(web3_strict_types, arguments, expected_types): + Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS) + + abi = Contract._find_matching_fn_abi('a', arguments) + assert abi['name'] == 'a' + assert len(abi['inputs']) == len(expected_types) + assert set(get_abi_input_types(abi)) == set(expected_types) diff --git a/tests/core/contracts/test_extracting_event_data.py b/tests/core/contracts/test_extracting_event_data.py index 54c9340896..a82eeeec57 100644 --- a/tests/core/contracts/test_extracting_event_data.py +++ b/tests/core/contracts/test_extracting_event_data.py @@ -13,6 +13,7 @@ ) from web3.exceptions import ( LogTopicError, + ValidationError, ) from web3.logs import ( DISCARD, @@ -41,6 +42,33 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_convers return _emitter +@pytest.fixture() +def StrictEmitter(web3_strict_types, EMITTER): + return web3_strict_types.eth.contract(**EMITTER) + + +@pytest.fixture() +def strict_emitter( + web3_strict_types, + StrictEmitter, + wait_for_transaction, + wait_for_block, + address_conversion_func): + + wait_for_block(web3_strict_types) + deploy_txn_hash = StrictEmitter.constructor().transact( + {'from': web3_strict_types.eth.coinbase, 'gas': 1000000} + ) + deploy_receipt = web3_strict_types.eth.waitForTransactionReceipt(deploy_txn_hash) + contract_address = address_conversion_func(deploy_receipt['contractAddress']) + + bytecode = web3_strict_types.eth.getCode(contract_address) + assert bytecode == StrictEmitter.bytecode_runtime + _emitter = StrictEmitter(address=contract_address) + assert _emitter.address == contract_address + return _emitter + + @pytest.fixture() def EventContract(web3, EVENT_CONTRACT): return web3.eth.contract(**EVENT_CONTRACT) @@ -176,7 +204,61 @@ def test_event_data_extraction(web3, else: assert event_topic in log_entry['topics'] - event_data = get_event_data(event_abi, log_entry) + event_data = get_event_data(web3.codec, event_abi, log_entry) + + assert event_data['args'] == expected_args + assert event_data['blockHash'] == txn_receipt['blockHash'] + assert event_data['blockNumber'] == txn_receipt['blockNumber'] + assert event_data['transactionIndex'] == txn_receipt['transactionIndex'] + assert is_same_address(event_data['address'], emitter.address) + assert event_data['event'] == event_name + + +@pytest.mark.parametrize( + 'contract_fn,event_name,call_args,expected_args', + ( + ( + 'logListArgs', + 'LogListArgs', + [[b'13'], [b'54']], + { + 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 + 'arg1': [b'54'] + } + ), + ( + 'logListArgs', + 'LogListArgs', + [[b'1'], [b'5']], + { + 'arg0': b' F=9\n\x03\xb6\xe1\x00\xc5\xb7\xce\xf5\xa5\xac\x08\x08\xb8\xaf\xc4d=\xdb\xda\xf1\x05|a\x0f.\xa1!', # noqa: E501 + 'arg1': [b'5\x00']} + ), + ) +) +def test_event_data_extraction_bytes(web3, + emitter, + wait_for_transaction, + emitter_log_topics, + emitter_event_ids, + contract_fn, + event_name, + call_args, + expected_args): + emitter_fn = emitter.functions[contract_fn] + txn_hash = emitter_fn(*call_args).transact() + txn_receipt = wait_for_transaction(web3, txn_hash) + + assert len(txn_receipt['logs']) == 1 + log_entry = txn_receipt['logs'][0] + + event_abi = emitter._find_matching_event_abi(event_name) + + event_topic = getattr(emitter_log_topics, event_name) + + assert event_topic in log_entry['topics'] + + event_data = get_event_data(web3.codec, event_abi, log_entry) assert event_data['args'] == expected_args assert event_data['blockHash'] == txn_receipt['blockHash'] @@ -186,6 +268,87 @@ def test_event_data_extraction(web3, assert event_data['event'] == event_name +@pytest.mark.parametrize( + 'contract_fn,event_name,call_args,expected_args', + ( + ( + 'logListArgs', + 'LogListArgs', + [['13'], ['54']], + { + 'arg0': b']\x0b\xf6sp\xbe\xa2L\xa9is\xe4\xab\xb7\xfa+nVJpgt\xa7\x8f:\xa4\x9f\xdb\x93\xf0\x8f\xae', # noqa: E501 + 'arg1': [b'T\x00'] + } + ), + ) +) +def test_event_data_extraction_bytes_with_warning(web3, + emitter, + wait_for_transaction, + emitter_log_topics, + emitter_event_ids, + contract_fn, + event_name, + call_args, + expected_args): + emitter_fn = emitter.functions[contract_fn] + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + txn_hash = emitter_fn(*call_args).transact() + txn_receipt = wait_for_transaction(web3, txn_hash) + + assert len(txn_receipt['logs']) == 1 + log_entry = txn_receipt['logs'][0] + + event_abi = emitter._find_matching_event_abi(event_name) + + event_topic = getattr(emitter_log_topics, event_name) + + assert event_topic in log_entry['topics'] + + event_data = get_event_data(web3.codec, event_abi, log_entry) + + assert event_data['args'] == expected_args + assert event_data['blockHash'] == txn_receipt['blockHash'] + assert event_data['blockNumber'] == txn_receipt['blockNumber'] + assert event_data['transactionIndex'] == txn_receipt['transactionIndex'] + assert is_same_address(event_data['address'], emitter.address) + assert event_data['event'] == event_name + + +@pytest.mark.parametrize( + 'contract_fn,event_name,call_args,expected_error', + ( + ( + 'logListArgs', + 'LogListArgs', + [[b'1312'], [b'4354']], + ValidationError, + ), + ( + 'logListArgs', + 'LogListArgs', + [[b'1'], [b'5']], + ValidationError, + ), + ) +) +def test_event_data_extraction_bytes_strict_with_errors(web3_strict_types, + strict_emitter, + wait_for_transaction, + emitter_log_topics, + emitter_event_ids, + contract_fn, + event_name, + call_args, + expected_error): + emitter_fn = strict_emitter.functions[contract_fn] + with pytest.raises(expected_error): + emitter_fn(*call_args).transact() + + def test_dynamic_length_argument_extraction(web3, emitter, wait_for_transaction, @@ -207,7 +370,7 @@ def test_dynamic_length_argument_extraction(web3, string_0_topic = web3.keccak(text=string_0) assert string_0_topic in log_entry['topics'] - event_data = get_event_data(event_abi, log_entry) + event_data = get_event_data(web3.codec, event_abi, log_entry) expected_args = { "arg0": string_0_topic, @@ -222,6 +385,44 @@ def test_dynamic_length_argument_extraction(web3, assert event_data['event'] == 'LogDynamicArgs' +def test_argument_extraction_strict_bytes_types(web3_strict_types, + strict_emitter, + wait_for_transaction, + emitter_log_topics): + arg_0 = [b'12'] + arg_1 = [b'12'] + txn_hash = strict_emitter.functions.logListArgs(arg_0, arg_1).transact() + txn_receipt = wait_for_transaction(web3_strict_types, txn_hash) + + assert len(txn_receipt['logs']) == 1 + log_entry = txn_receipt['logs'][0] + assert len(log_entry['topics']) == 2 + + event_abi = strict_emitter._find_matching_event_abi('LogListArgs') + + event_topic = emitter_log_topics.LogListArgs + assert event_topic in log_entry['topics'] + + encoded_arg_0 = web3_strict_types.codec.encode_abi(['bytes2'], arg_0) + padded_arg_0 = encoded_arg_0.ljust(32, b'\x00') + arg_0_topic = web3_strict_types.keccak(padded_arg_0) + assert arg_0_topic in log_entry['topics'] + + event_data = get_event_data(web3_strict_types.codec, event_abi, log_entry) + + expected_args = { + "arg0": arg_0_topic, + "arg1": arg_1 + } + + assert event_data['args'] == expected_args + assert event_data['blockHash'] == txn_receipt['blockHash'] + assert event_data['blockNumber'] == txn_receipt['blockNumber'] + assert event_data['transactionIndex'] == txn_receipt['transactionIndex'] + assert is_same_address(event_data['address'], strict_emitter.address) + assert event_data['event'] == 'LogListArgs' + + @pytest.mark.parametrize( 'contract_fn,event_name,call_args,expected_args,warning_msg,process_receipt', ( @@ -463,6 +664,68 @@ def test_event_rich_log( assert empty_rich_log == tuple() +@pytest.mark.parametrize( + 'contract_fn,event_name,call_args,expected_args,process_receipt', + ( + ( + 'logListArgs', + 'LogListArgs', + [[b'13'], [b'54']], + { + 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 + 'arg1': [b'54'] + }, + True + ), + ( + 'logListArgs', + 'LogListArgs', + [[b'13'], [b'54']], + { + 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 + 'arg1': [b'54'] + }, + False + ), + ) +) +def test_event_rich_log_with_byte_args( + web3, + emitter, + emitter_event_ids, + wait_for_transaction, + contract_fn, + event_name, + call_args, + process_receipt, + expected_args): + + emitter_fn = emitter.functions[contract_fn] + txn_hash = emitter_fn(*call_args).transact() + txn_receipt = wait_for_transaction(web3, txn_hash) + + event_instance = emitter.events[event_name]() + + if process_receipt: + processed_logs = event_instance.processReceipt(txn_receipt) + assert len(processed_logs) == 1 + rich_log = processed_logs[0] + elif not process_receipt: + rich_log = event_instance.processLog(txn_receipt['logs'][0]) + else: + raise Exception('Unreachable!') + + assert rich_log['args'] == expected_args + assert rich_log.args == expected_args + for arg in expected_args: + assert getattr(rich_log.args, arg) == expected_args[arg] + assert rich_log['blockHash'] == txn_receipt['blockHash'] + assert rich_log['blockNumber'] == txn_receipt['blockNumber'] + assert rich_log['transactionIndex'] == txn_receipt['transactionIndex'] + assert is_same_address(rich_log['address'], emitter.address) + assert rich_log['event'] == event_name + + def test_receipt_processing_with_discard_flag( web3, event_contract, diff --git a/tests/core/contracts/test_extracting_event_data_old.py b/tests/core/contracts/test_extracting_event_data_old.py index 9d5a79e7f1..e100762125 100644 --- a/tests/core/contracts/test_extracting_event_data_old.py +++ b/tests/core/contracts/test_extracting_event_data_old.py @@ -95,7 +95,7 @@ def test_event_data_extraction(web3, else: assert event_topic in log_entry['topics'] - event_data = get_event_data(event_abi, log_entry) + event_data = get_event_data(web3.codec, event_abi, log_entry) assert event_data['args'] == expected_args assert event_data['blockHash'] == txn_receipt['blockHash'] @@ -126,7 +126,7 @@ def test_dynamic_length_argument_extraction(web3, string_0_topic = web3.keccak(text=string_0) assert string_0_topic in log_entry['topics'] - event_data = get_event_data(event_abi, log_entry) + event_data = get_event_data(web3.codec, event_abi, log_entry) expected_args = { "arg0": string_0_topic, diff --git a/tests/core/filtering/test_utils_functions.py b/tests/core/filtering/test_utils_functions.py index 53f3f8e34d..a72601e20a 100644 --- a/tests/core/filtering/test_utils_functions.py +++ b/tests/core/filtering/test_utils_functions.py @@ -3,10 +3,6 @@ ) import pytest -from eth_abi import ( - encode_abi, -) - from web3._utils.filters import ( match_fn, ) @@ -83,19 +79,19 @@ ), ) ) -def test_match_fn_with_various_data_types(data, expected, match_data_and_abi): +def test_match_fn_with_various_data_types(web3, data, expected, match_data_and_abi): abi_types, match_data = zip(*match_data_and_abi) - encoded_data = encode_abi(abi_types, data) - assert match_fn(match_data_and_abi, encoded_data) == expected + encoded_data = web3.codec.encode_abi(abi_types, data) + assert match_fn(web3, match_data_and_abi, encoded_data) == expected -def test_wrong_type_match_data(): +def test_wrong_type_match_data(web3): data = ("hello", "goodbye") match_data_and_abi = ( ("string", (50505050,)), ("string", (50505050,)), ) abi_types, match_data = zip(*match_data_and_abi) - encoded_data = encode_abi(abi_types, data) + encoded_data = web3.codec.encode_abi(abi_types, data) with pytest.raises(ValueError): - match_fn(match_data_and_abi, encoded_data) + match_fn(web3, match_data_and_abi, encoded_data) diff --git a/tests/core/shh-module/test_shh_post.py b/tests/core/shh-module/test_shh_post.py index ff69eb0c63..44ad769bc1 100644 --- a/tests/core/shh-module/test_shh_post.py +++ b/tests/core/shh-module/test_shh_post.py @@ -1,10 +1,24 @@ -def test_shh_post(web3, skip_if_testrpc): - skip_if_testrpc(web3) - receiver_pub = web3.shh.getPublicKey(web3.shh.newKeyPair()) - assert web3.shh.post({ - "topic": "0x12345678", - "powTarget": 2.5, - "powTime": 2, - "payload": web3.toHex(text="testing shh on web3.py"), - "pubKey": receiver_pub, - }) +# def test_shh_post(web3, skip_if_testrpc): +# skip_if_testrpc(web3) +# assert 0 +# receiver_pub = web3.shh.getPublicKey(web3.shh.newKeyPair()) +# try: +# response = web3.shh.post({ +# "topic": "0x12345678", +# "powTarget": 2.5, +# "powTime": 2, +# "payload": web3.toHex(text="testing shh on web3.py"), +# "pubKey": receiver_pub, +# }) +# except ValueError: +# # with pytest.raises(ValueError): +# response = web3.shh.post({ +# "topic": "0x12345678", +# "powTarget": 2.5, +# "powTime": 2, +# "payload": web3.toHex(text="testing shh on web3.py"), +# "pubKey": receiver_pub, +# }) +# assert False +# else: +# assert response is False diff --git a/tests/core/utilities/test_abi_is_encodable.py b/tests/core/utilities/test_abi_is_encodable.py index af357fd657..3a78389b00 100644 --- a/tests/core/utilities/test_abi_is_encodable.py +++ b/tests/core/utilities/test_abi_is_encodable.py @@ -1,7 +1,8 @@ import pytest -from web3._utils.abi import ( - is_encodable, +from web3 import ( + EthereumTesterProvider, + Web3, ) @@ -39,16 +40,13 @@ ('rejects_invalid_names.eth', 'address', False), # no underscore in domain names # Special bytes behavior - ('12', 'bytes2', True), # undersize OK ('0x12', 'bytes2', True), # with or without 0x OK - ('0123', 'bytes2', True), # exact size OK (b'\x12', 'bytes2', True), # as bytes value undersize OK ('0123', 'bytes1', False), # no oversize hex strings ('1', 'bytes2', False), # no odd length ('0x1', 'bytes2', False), # no odd length # Special bytes behavior - ('12', 'bytes', True), ('0x12', 'bytes', True), ('1', 'bytes', False), ('0x1', 'bytes', False), @@ -73,5 +71,58 @@ ), ) def test_is_encodable(value, _type, expected): - actual = is_encodable(_type, value) + w3 = Web3(EthereumTesterProvider()) + actual = w3.is_encodable(_type, value) + assert actual is expected + + +@pytest.mark.parametrize( + 'value,_type,expected', + ( + ('12', 'bytes2', True), + ('0123', 'bytes2', True), + + ('12', 'bytes', True), + ) +) +def test_is_encodable_warnings(value, _type, expected): + w3 = Web3(EthereumTesterProvider()) + with pytest.warns( + DeprecationWarning, + match='in v6 it will be invalid to pass a hex string without the "0x" prefix' + ): + actual = w3.is_encodable(_type, value) + assert actual is expected + + +@pytest.mark.parametrize( + 'value,_type,expected', + ( + # Special bytes behavior + ('12', 'bytes2', False), # no hex strings without leading 0x + ('0x12', 'bytes1', True), # with 0x OK + ('0123', 'bytes2', False), # needs a 0x + (b'\x12', 'bytes2', False), # no undersize bytes value + ('0123', 'bytes1', False), # no oversize hex strings + ('1', 'bytes2', False), # no odd length + ('0x1', 'bytes2', False), # no odd length + + # Special bytes behavior + ('12', 'bytes', False), # has to have 0x if string + ('0x12', 'bytes', True), + ('1', 'bytes', False), + ('0x1', 'bytes', False), + ('0x0x0x0x', 'bytes', False), + + # Special string behavior + (b'', 'string', True), + (b'anything', 'string', True), + (b'\x80', 'string', False), # bytes that cannot be decoded with utf-8 are invalid + ), +) +def test_is_encodable_strict(value, _type, expected): + w3 = Web3(EthereumTesterProvider()) + w3.enable_strict_bytes_type_checking() + + actual = w3.is_encodable(_type, value) assert actual is expected diff --git a/tests/core/utilities/test_construct_event_data_set.py b/tests/core/utilities/test_construct_event_data_set.py index 0cc783f1ed..f6f341d453 100644 --- a/tests/core/utilities/test_construct_event_data_set.py +++ b/tests/core/utilities/test_construct_event_data_set.py @@ -1,5 +1,10 @@ import pytest +from eth_abi.exceptions import ( + EncodingTypeError, + ValueOutOfBounds, +) + from web3._utils.events import ( construct_event_data_set, ) @@ -19,6 +24,16 @@ } EVENT_1_TOPIC = '0xa7144ed450ecab4a6283d3b1e290ff6c889232d922b84d88203eb7619222fb32' +EVENT_2_ABI = { + "anonymous": False, + "inputs": [ + {"indexed": False, "name": "arg0", "type": "bytes3"}, + ], + "name": "Event_2", + "type": "event", +} +EVENT_2_TOPIC = '0x84fa8d791e38d043e0c66b2437051fd24d32b1022f91a754123d8e1746e98453' + def hex_and_pad(i): unpadded_hex_value = hex(i).rstrip('L') @@ -68,6 +83,87 @@ def hex_and_pad(i): ), ) ) -def test_construct_event_data_set(event_abi, arguments, expected): - actual = construct_event_data_set(event_abi, arguments) +def test_construct_event_data_set(web3, event_abi, arguments, expected): + actual = construct_event_data_set(event_abi, web3.codec, arguments) + assert actual == expected + + +@pytest.mark.parametrize( + 'event_abi,arguments,expected', + ( + ( + EVENT_1_ABI, + {}, + [[]], + ), + ( + EVENT_1_ABI, + {'arg1': 1}, + [[]], + ), + ( + EVENT_1_ABI, + {'arg0': 1}, + [[hex_and_pad(1), None, None]], + ), + ( + EVENT_1_ABI, + {'arg0': [1]}, + [[hex_and_pad(1), None, None]], + ), + ( + EVENT_1_ABI, + {'arg0': [1, 2]}, + [ + [hex_and_pad(1), None, None], + [hex_and_pad(2), None, None], + ], + ), + ( + EVENT_1_ABI, + {'arg0': [1, 3], 'arg3': [2, 4]}, + [ + [hex_and_pad(1), hex_and_pad(2), None], + [hex_and_pad(1), hex_and_pad(4), None], + [hex_and_pad(3), hex_and_pad(2), None], + [hex_and_pad(3), hex_and_pad(4), None], + ], + ), + ) +) +def test_construct_event_data_set_strict(web3_strict_types, event_abi, arguments, expected): + actual = construct_event_data_set(event_abi, web3_strict_types.codec, arguments) assert actual == expected + + +@pytest.mark.parametrize( + 'event_abi,arguments,expected_error', + ( + ( + EVENT_2_ABI, + {'arg0': '131414'}, + EncodingTypeError, + ), + ( + EVENT_2_ABI, + {'arg0': b'131414'}, + ValueOutOfBounds, + ), + ( + EVENT_2_ABI, + {'arg0': b'13'}, + ValueOutOfBounds, + ), + ( + EVENT_2_ABI, + {'arg0': b'12'}, + ValueOutOfBounds, + ), + ) +) +def test_construct_event_data_set_strict_with_errors(web3_strict_types, + event_abi, + arguments, + expected_error): + with pytest.raises(expected_error): + construct_event_data_set(event_abi, web3_strict_types.codec, arguments) diff --git a/tests/core/utilities/test_construct_event_filter_params.py b/tests/core/utilities/test_construct_event_filter_params.py index 956e35a5e7..882d04ef5b 100644 --- a/tests/core/utilities/test_construct_event_filter_params.py +++ b/tests/core/utilities/test_construct_event_filter_params.py @@ -46,8 +46,8 @@ }), ), ) -def test_construct_event_filter_params(event_abi, fn_kwargs, expected): - _, actual = construct_event_filter_params(event_abi, **fn_kwargs) +def test_construct_event_filter_params(web3, event_abi, fn_kwargs, expected): + _, actual = construct_event_filter_params(event_abi, web3.codec, **fn_kwargs) assert actual == expected @@ -68,7 +68,7 @@ def hex_and_pad(i): ]), ), ) -def test_construct_event_filter_params_for_data_filters(event_abi, fn_kwargs, +def test_construct_event_filter_params_for_data_filters(event_abi, web3, fn_kwargs, expected): - actual, _ = construct_event_filter_params(event_abi, **fn_kwargs) + actual, _ = construct_event_filter_params(event_abi, web3.codec, **fn_kwargs) assert actual == expected diff --git a/tests/core/utilities/test_construct_event_topics.py b/tests/core/utilities/test_construct_event_topics.py index 3bc4b5fafa..5190904d26 100644 --- a/tests/core/utilities/test_construct_event_topics.py +++ b/tests/core/utilities/test_construct_event_topics.py @@ -1,5 +1,10 @@ import pytest +from eth_abi.exceptions import ( + EncodingTypeError, + ValueOutOfBounds, +) + from web3._utils.events import ( construct_event_topic_set, ) @@ -18,6 +23,16 @@ "type": "event", } EVENT_1_TOPIC = '0xa7144ed450ecab4a6283d3b1e290ff6c889232d922b84d88203eb7619222fb32' +EVENT_2_TOPIC = '0xe74d4a9355b06f414793e46ef1aed5b520bf68289bbd21b6bbfdbf4154451d64' +EVENT_2_ABI = { + "anonymous": False, + "inputs": [ + {"indexed": True, "name": "arg0", "type": "bytes2"}, + {"indexed": True, "name": "arg1", "type": "bytes2"}, + ], + "name": "Event_2", + "type": "event", +} def hex_and_pad(i): @@ -75,6 +90,99 @@ def hex_and_pad(i): ), ) ) -def test_construct_event_topics(event_abi, arguments, expected): - actual = construct_event_topic_set(event_abi, arguments) +def test_construct_event_topics(web3, event_abi, arguments, expected): + actual = construct_event_topic_set(event_abi, web3.codec, arguments) + assert actual == expected + + +@pytest.mark.parametrize( + 'event_abi,arguments,expected', + ( + ( + EVENT_1_ABI, + {}, + [EVENT_1_TOPIC], + ), + ( + EVENT_1_ABI, + {'arg0': 1}, + [EVENT_1_TOPIC], + ), + ( + EVENT_1_ABI, + {'arg0': 1, 'arg3': [1, 2]}, + [EVENT_1_TOPIC], + ), + ( + EVENT_1_ABI, + {'arg1': 1}, + [ + EVENT_1_TOPIC, hex_and_pad(1) + ], + ), + ( + EVENT_1_ABI, + {'arg1': [1, 2]}, + [ + EVENT_1_TOPIC, [hex_and_pad(1), hex_and_pad(2)], + ], + ), + ( + EVENT_1_ABI, + {'arg1': [1], 'arg2': [2]}, + [ + EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), + ], + ), + ( + EVENT_1_ABI, + {'arg1': [1, 3], 'arg2': [2, 4]}, + [ + EVENT_1_TOPIC, + [hex_and_pad(1), hex_and_pad(3)], + [hex_and_pad(2), hex_and_pad(4)] + ], + ), + ) +) +def test_construct_event_topics_strict(web3_strict_types, event_abi, arguments, expected): + actual = construct_event_topic_set(event_abi, web3_strict_types.codec, arguments) assert actual == expected + + +@pytest.mark.parametrize( + 'event_abi,arguments,expected,error', + ( + ( + EVENT_2_ABI, + {'arg0': [b'123412']}, + [EVENT_2_TOPIC], + ValueOutOfBounds, + ), + ( + EVENT_2_ABI, + {'arg1': [b'']}, + [EVENT_2_TOPIC], + ValueOutOfBounds, + ), + ( + EVENT_2_ABI, + {'arg0': [b''], 'arg1': [b'']}, + [EVENT_2_TOPIC], + ValueOutOfBounds, + ), + ( + EVENT_2_ABI, + {'arg0': ['']}, + [EVENT_2_TOPIC], + EncodingTypeError, + ), + ) +) +def test_construct_event_topics_strict_errors(web3_strict_types, + event_abi, + arguments, + expected, + error): + with pytest.raises(error): + construct_event_topic_set(event_abi, web3_strict_types.codec, arguments) diff --git a/tests/core/utilities/test_event_filter_builder.py b/tests/core/utilities/test_event_filter_builder.py index 8dfc0ead3d..6222e8f2cd 100644 --- a/tests/core/utilities/test_event_filter_builder.py +++ b/tests/core/utilities/test_event_filter_builder.py @@ -40,13 +40,13 @@ def test_match_single_string_type_properties_data_arg(value): @given(st.text()) -def test_match_single_string_type_properties_topic_arg(value): - topic_filter = TopicArgumentFilter(arg_type="string") +def test_match_single_string_type_properties_topic_arg(web3, value): + topic_filter = TopicArgumentFilter(arg_type="string", abi_codec=web3.codec) topic_filter.match_single(value) @given(st.lists(elements=st.text(), max_size=10, min_size=0)) -def test_match_any_string_type_properties(values): - topic_filter = TopicArgumentFilter(arg_type="string") +def test_match_any_string_type_properties(web3, values): + topic_filter = TopicArgumentFilter(arg_type="string", abi_codec=web3.codec) topic_filter.match_any(*values) assert len(topic_filter.match_values) == len(values) diff --git a/tests/core/utilities/test_validation.py b/tests/core/utilities/test_validation.py index 0dfe5359ee..ab8850a464 100644 --- a/tests/core/utilities/test_validation.py +++ b/tests/core/utilities/test_validation.py @@ -135,6 +135,8 @@ def test_validation(param, validation, expected): ('bytes', "0x5402", None), ('bytes', "5402", TypeError), ('bytes', b'T\x02', None), + ('bytes2', b'T\x02', None), + ('bytes3', b'T\x02', None), ) ) def test_validate_abi_value(abi_type, value, expected): diff --git a/web3/_utils/abi.py b/web3/_utils/abi.py index 851fcd5dca..b66586e590 100644 --- a/web3/_utils/abi.py +++ b/web3/_utils/abi.py @@ -12,13 +12,17 @@ Tuple, Union, ) +import warnings from eth_abi import ( decoding, encoding, ) -from eth_abi.codec import ( - ABICodec, +from eth_abi.base import ( + parse_type_str, +) +from eth_abi.exceptions import ( + ValueOutOfBounds, ) from eth_abi.grammar import ( ABIType, @@ -49,6 +53,9 @@ pipe, ) +from web3._utils.decorators import ( + combomethod, +) from web3._utils.ens import ( is_ens_name, ) @@ -146,25 +153,131 @@ def validate_value(cls, value): class AcceptsHexStrMixin: + def validate(self): + super().validate() + + if self.is_strict is None: + raise ValueError("`is_strict` may not be none") + + @combomethod def validate_value(self, value): + original_value = value if is_text(value): try: value = decode_hex(value) except binascii.Error: self.invalidate_value( value, - msg='invalid hex string', + msg=f'{value} is an invalid hex string', ) + else: + self.check_for_0x(original_value) super().validate_value(value) + def check_for_0x(self, original_value): + if original_value[:2] != '0x': + if self.is_strict: + self.invalidate_value( + original_value, + msg='hex string must be prefixed with 0x' + ) + elif original_value[:2] != '0x': + warnings.warn( + 'in v6 it will be invalid to pass a hex string without the "0x" prefix', + category=DeprecationWarning + ) + class BytesEncoder(AcceptsHexStrMixin, encoding.BytesEncoder): - pass + is_strict = False class ByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder): - pass + is_strict = False + + +class StrictByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder): + is_strict = True + + +class ExactLengthBytesEncoder(encoding.BaseEncoder): + # TODO: move this to eth-abi once the api is stabilized + is_big_endian = False + value_bit_size = None + data_byte_size = None + encode_fn = None + + def validate(self): + super().validate() + + if self.value_bit_size is None: + raise ValueError("`value_bit_size` may not be none") + if self.data_byte_size is None: + raise ValueError("`data_byte_size` may not be none") + if self.encode_fn is None: + raise ValueError("`encode_fn` may not be none") + if self.is_big_endian is None: + raise ValueError("`is_big_endian` may not be none") + + if self.value_bit_size % 8 != 0: + raise ValueError( + "Invalid value bit size: {0}. Must be a multiple of 8".format( + self.value_bit_size, + ) + ) + + if self.value_bit_size > self.data_byte_size * 8: + raise ValueError("Value byte size exceeds data size") + + def encode(self, value): + self.validate_value(value) + return self.encode_fn(value) + + def validate_value(self, value): + if not is_bytes(value) and not is_text(value): + self.invalidate_value(value) + + original_value = value + if is_text(value): + try: + value = decode_hex(value) + except binascii.Error: + self.invalidate_value( + value, + msg=f'{value} is an invalid hex string', + ) + else: + if original_value[:2] != '0x': + self.invalidate_value( + original_value, + msg='hex string must be prefixed with 0x' + ) + + byte_size = self.value_bit_size // 8 + if len(value) > byte_size: + self.invalidate_value( + value, + exc=ValueOutOfBounds, + msg="exceeds total byte size for bytes{} encoding".format(byte_size), + ) + elif len(value) < byte_size: + self.invalidate_value( + value, + exc=ValueOutOfBounds, + msg="less than total byte size for bytes{} encoding".format(byte_size), + ) + + @staticmethod + def encode_fn(value): + return value + + @parse_type_str('bytes') + def from_type_str(cls, abi_type, registry): + return cls( + value_bit_size=abi_type.sub * 8, + data_byte_size=abi_type.sub, + ) class TextStringEncoder(encoding.TextStringEncoder): @@ -182,50 +295,16 @@ def validate_value(cls, value): super().validate_value(value) -# We make a copy here just to make sure that eth-abi's default registry is not -# affected by our custom encoder subclasses -registry = default_registry.copy() - -registry.unregister('address') -registry.unregister('bytes') -registry.unregister('bytes') -registry.unregister('string') - -registry.register( - BaseEquals('address'), - AddressEncoder, decoding.AddressDecoder, - label='address', -) -registry.register( - BaseEquals('bytes', with_sub=True), - BytesEncoder, decoding.BytesDecoder, - label='bytes', -) -registry.register( - BaseEquals('bytes', with_sub=False), - ByteStringEncoder, decoding.ByteStringDecoder, - label='bytes', -) -registry.register( - BaseEquals('string'), - TextStringEncoder, decoding.StringDecoder, - label='string', -) - -codec = ABICodec(registry) -is_encodable = codec.is_encodable - - -def filter_by_encodability(args, kwargs, contract_abi): +def filter_by_encodability(abi_codec, args, kwargs, contract_abi): return [ function_abi for function_abi in contract_abi - if check_if_arguments_can_be_encoded(function_abi, args, kwargs) + if check_if_arguments_can_be_encoded(function_abi, abi_codec, args, kwargs) ] -def check_if_arguments_can_be_encoded(function_abi, args, kwargs): +def check_if_arguments_can_be_encoded(function_abi, abi_codec, args, kwargs): try: arguments = merge_args_and_kwargs(function_abi, args, kwargs) except TypeError: @@ -240,7 +319,7 @@ def check_if_arguments_can_be_encoded(function_abi, args, kwargs): return False return all( - is_encodable(_type, arg) + abi_codec.is_encodable(_type, arg) for _type, arg in zip(types, aligned_args) ) @@ -711,3 +790,67 @@ def strip_abi_type(elements): return elements.data else: return elements + + +def build_default_registry(): + # We make a copy here just to make sure that eth-abi's default registry is not + # affected by our custom encoder subclasses + registry = default_registry.copy() + + registry.unregister('address') + registry.unregister('bytes') + registry.unregister('bytes') + registry.unregister('string') + + registry.register( + BaseEquals('address'), + AddressEncoder, decoding.AddressDecoder, + label='address', + ) + registry.register( + BaseEquals('bytes', with_sub=True), + BytesEncoder, decoding.BytesDecoder, + label='bytes', + ) + registry.register( + BaseEquals('bytes', with_sub=False), + ByteStringEncoder, decoding.ByteStringDecoder, + label='bytes', + ) + registry.register( + BaseEquals('string'), + TextStringEncoder, decoding.StringDecoder, + label='string', + ) + return registry + + +def build_strict_registry(): + registry = default_registry.copy() + + registry.unregister('address') + registry.unregister('bytes') + registry.unregister('bytes') + registry.unregister('string') + + registry.register( + BaseEquals('address'), + AddressEncoder, decoding.AddressDecoder, + label='address', + ) + registry.register( + BaseEquals('bytes', with_sub=True), + ExactLengthBytesEncoder, decoding.BytesDecoder, + label='bytes', + ) + registry.register( + BaseEquals('bytes', with_sub=False), + StrictByteStringEncoder, decoding.ByteStringDecoder, + label='bytes', + ) + registry.register( + BaseEquals('string'), + TextStringEncoder, decoding.StringDecoder, + label='string', + ) + return registry diff --git a/web3/_utils/contracts.py b/web3/_utils/contracts.py index 90ecc17e93..cebd153e28 100644 --- a/web3/_utils/contracts.py +++ b/web3/_utils/contracts.py @@ -1,8 +1,5 @@ import functools -from eth_abi import ( - encode_abi as eth_abi_encode_abi, -) from eth_utils import ( add_0x_prefix, encode_hex, @@ -72,7 +69,7 @@ def find_matching_event_abi(abi, event_name=None, argument_names=None): raise ValueError("Multiple events found") -def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): +def find_matching_fn_abi(abi, abi_codec, fn_identifier=None, args=None, kwargs=None): args = args or tuple() kwargs = kwargs or dict() num_arguments = len(args) + len(kwargs) @@ -85,7 +82,7 @@ def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) - encoding_filter = functools.partial(filter_by_encodability, args, kwargs) + encoding_filter = functools.partial(filter_by_encodability, abi_codec, args, kwargs) function_candidates = pipe(abi, name_filter, arg_count_filter, encoding_filter) @@ -129,7 +126,7 @@ def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): def encode_abi(web3, abi, arguments, data=None): argument_types = get_abi_input_types(abi) - if not check_if_arguments_can_be_encoded(abi, arguments, {}): + if not check_if_arguments_can_be_encoded(abi, web3.codec, arguments, {}): raise TypeError( "One or more arguments could not be encoded to the necessary " "ABI type. Expected types are: {0}".format( @@ -148,7 +145,7 @@ def encode_abi(web3, abi, arguments, data=None): argument_types, arguments, ) - encoded_arguments = eth_abi_encode_abi( + encoded_arguments = web3.codec.encode_abi( argument_types, normalized_arguments, ) @@ -175,7 +172,7 @@ def prepare_transaction( TODO: add new prepare_deploy_transaction API """ if fn_abi is None: - fn_abi = find_matching_fn_abi(contract_abi, fn_identifier, fn_args, fn_kwargs) + fn_abi = find_matching_fn_abi(contract_abi, web3.codec, fn_identifier, fn_args, fn_kwargs) validate_payable(transaction, fn_abi) @@ -212,7 +209,7 @@ def encode_transaction_data( fn_abi, fn_selector, fn_arguments = get_fallback_function_info(contract_abi, fn_abi) elif is_text(fn_identifier): fn_abi, fn_selector, fn_arguments = get_function_info( - fn_identifier, contract_abi, fn_abi, args, kwargs, + fn_identifier, web3.codec, contract_abi, fn_abi, args, kwargs, ) else: raise TypeError("Unsupported function identifier") @@ -228,14 +225,14 @@ def get_fallback_function_info(contract_abi=None, fn_abi=None): return fn_abi, fn_selector, fn_arguments -def get_function_info(fn_name, contract_abi=None, fn_abi=None, args=None, kwargs=None): +def get_function_info(fn_name, abi_codec, contract_abi=None, fn_abi=None, args=None, kwargs=None): if args is None: args = tuple() if kwargs is None: kwargs = {} if fn_abi is None: - fn_abi = find_matching_fn_abi(contract_abi, fn_name, args, kwargs) + fn_abi = find_matching_fn_abi(contract_abi, abi_codec, fn_name, args, kwargs) fn_selector = encode_hex(function_abi_to_4byte_selector(fn_abi)) diff --git a/web3/_utils/events.py b/web3/_utils/events.py index 67b993e612..6deca9d848 100644 --- a/web3/_utils/events.py +++ b/web3/_utils/events.py @@ -6,9 +6,6 @@ import itertools from eth_abi import ( - decode_abi, - decode_single, - encode_single, grammar, ) from eth_typing import ( @@ -61,7 +58,7 @@ ) -def construct_event_topic_set(event_abi, arguments=None): +def construct_event_topic_set(event_abi, abi_codec, arguments=None): if arguments is None: arguments = {} if isinstance(arguments, (list, tuple)): @@ -89,7 +86,7 @@ def construct_event_topic_set(event_abi, arguments=None): ] encoded_args = [ [ - None if option is None else encode_hex(encode_single(arg['type'], option)) + None if option is None else encode_hex(abi_codec.encode_single(arg['type'], option)) for option in arg_options] for arg, arg_options in zipped_abi_and_args ] @@ -98,7 +95,7 @@ def construct_event_topic_set(event_abi, arguments=None): return topics -def construct_event_data_set(event_abi, arguments=None): +def construct_event_data_set(event_abi, abi_codec, arguments=None): if arguments is None: arguments = {} if isinstance(arguments, (list, tuple)): @@ -125,7 +122,7 @@ def construct_event_data_set(event_abi, arguments=None): ] encoded_args = [ [ - None if option is None else encode_hex(encode_single(arg['type'], option)) + None if option is None else encode_hex(abi_codec.encode_single(arg['type'], option)) for option in arg_options] for arg, arg_options in zipped_abi_and_args ] @@ -159,7 +156,7 @@ def get_event_abi_types_for_decoding(event_inputs): @curry -def get_event_data(event_abi, log_entry): +def get_event_data(abi_codec, event_abi, log_entry): """ Given an event ABI and a log entry for that event, return the decoded event data @@ -199,7 +196,7 @@ def get_event_data(event_abi, log_entry): f"between event inputs: '{', '.join(duplicate_names)}'" ) - decoded_log_data = decode_abi(log_data_types, log_data) + decoded_log_data = abi_codec.decode_abi(log_data_types, log_data) normalized_log_data = map_abi_data( BASE_RETURN_NORMALIZERS, log_data_types, @@ -207,7 +204,7 @@ def get_event_data(event_abi, log_entry): ) decoded_topic_data = [ - decode_single(topic_type, topic_data) + abi_codec.decode_single(topic_type, topic_data) for topic_type, topic_data in zip(log_topic_types, log_topics) ] @@ -270,12 +267,13 @@ class EventFilterBuilder: _address = None _immutable = False - def __init__(self, event_abi, formatter=None): + def __init__(self, event_abi, abi_codec, formatter=None): self.event_abi = event_abi + self.abi_codec = abi_codec self.formatter = formatter self.event_topic = initialize_event_topics(self.event_abi) self.args = AttributeDict( - _build_argument_filters_from_event_abi(event_abi)) + _build_argument_filters_from_event_abi(event_abi, abi_codec)) self._ordered_arg_names = tuple(arg['name'] for arg in event_abi['inputs']) @property @@ -378,11 +376,11 @@ def initialize_event_topics(event_abi): @to_dict -def _build_argument_filters_from_event_abi(event_abi): +def _build_argument_filters_from_event_abi(event_abi, abi_codec): for item in event_abi['inputs']: key = item['name'] if item['indexed'] is True: - value = TopicArgumentFilter(arg_type=item['type']) + value = TopicArgumentFilter(abi_codec=abi_codec, arg_type=item['type']) else: value = DataArgumentFilter(arg_type=item['type']) yield key, value @@ -433,6 +431,10 @@ def match_values(self): class TopicArgumentFilter(BaseArgumentFilter): + def __init__(self, arg_type, abi_codec): + self.abi_codec = abi_codec + self.arg_type = arg_type + @to_tuple def _get_match_values(self): yield from (self._encode(value) for value in self._match_values) @@ -448,7 +450,7 @@ def _encode(self, value): if is_dynamic_sized_type(self.arg_type): return to_hex(keccak(encode_single_packed(self.arg_type, value))) else: - return to_hex(encode_single(self.arg_type, value)) + return to_hex(self.abi_codec.encode_single(self.arg_type, value)) class EventLogErrorFlags(Enum): diff --git a/web3/_utils/filters.py b/web3/_utils/filters.py index a6204012dc..ae44202c43 100644 --- a/web3/_utils/filters.py +++ b/web3/_utils/filters.py @@ -1,7 +1,3 @@ -from eth_abi import ( - decode_abi, - is_encodable, -) from eth_abi.grammar import ( parse as parse_type_string, ) @@ -35,6 +31,7 @@ def construct_event_filter_params(event_abi, + abi_codec, contract_address=None, argument_filters=None, topics=None, @@ -42,7 +39,7 @@ def construct_event_filter_params(event_abi, toBlock=None, address=None): filter_params = {} - topic_set = construct_event_topic_set(event_abi, argument_filters) + topic_set = construct_event_topic_set(event_abi, abi_codec, argument_filters) if topics is not None: if len(topic_set) > 1: @@ -84,7 +81,7 @@ def construct_event_filter_params(event_abi, if toBlock is not None: filter_params['toBlock'] = toBlock - data_filters_set = construct_event_data_set(event_abi, argument_filters) + data_filters_set = construct_event_data_set(event_abi, abi_codec, argument_filters) return data_filters_set, filter_params @@ -173,7 +170,7 @@ def set_data_filters(self, data_filter_set): """ self.data_filter_set = data_filter_set if any(data_filter_set): - self.data_filter_set_function = match_fn(data_filter_set) + self.data_filter_set_function = match_fn(self.web3, data_filter_set) def is_valid_entry(self, entry): if not self.data_filter_set: @@ -205,23 +202,24 @@ def normalize_data_values(type_string, data_value): @curry -def match_fn(match_values_and_abi, data): +def match_fn(w3, match_values_and_abi, data): """Match function used for filtering non-indexed event arguments. Values provided through the match_values_and_abi parameter are compared to the abi decoded log data. """ abi_types, all_match_values = zip(*match_values_and_abi) - decoded_values = decode_abi(abi_types, HexBytes(data)) + decoded_values = w3.codec.decode_abi(abi_types, HexBytes(data)) for data_value, match_values, abi_type in zip(decoded_values, all_match_values, abi_types): if match_values is None: continue normalized_data = normalize_data_values(abi_type, data_value) for value in match_values: - if not is_encodable(abi_type, value): + if not w3.is_encodable(abi_type, value): raise ValueError( - "Value {0} is of the wrong abi type. " - "Expected {1} typed value.".format(value, abi_type)) + f"Value {value} is of the wrong abi type. " + f"Expected {abi_type} typed value." + ) if value == normalized_data: break else: diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index e10e71a048..a656aac6c6 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -3,9 +3,6 @@ import json import pytest -from eth_abi import ( - decode_single, -) from eth_utils import ( is_boolean, is_bytes, @@ -569,7 +566,7 @@ def test_eth_call(self, web3, math_contract): ) call_result = web3.eth.call(txn_params) assert is_string(call_result) - result = decode_single('uint256', call_result) + result = web3.codec.decode_single('uint256', call_result) assert result == 18 def test_eth_call_with_0_result(self, web3, math_contract): @@ -581,7 +578,7 @@ def test_eth_call_with_0_result(self, web3, math_contract): ) call_result = web3.eth.call(txn_params) assert is_string(call_result) - result = decode_single('uint256', call_result) + result = web3.codec.decode_single('uint256', call_result) assert result == 0 def test_eth_estimateGas(self, web3, unlocked_account_dual_type): diff --git a/web3/contract.py b/web3/contract.py index b52a2e668a..10ff031ee4 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -5,9 +5,6 @@ import itertools import warnings -from eth_abi import ( - decode_abi, -) from eth_abi.exceptions import ( DecodingError, ) @@ -351,7 +348,7 @@ def encodeABI(cls, fn_name, args=None, kwargs=None, data=None): :param data: defaults to function selector """ fn_abi, fn_selector, fn_arguments = get_function_info( - fn_name, contract_abi=cls.abi, args=args, kwargs=kwargs, + fn_name, cls.web3.codec, contract_abi=cls.abi, args=args, kwargs=kwargs, ) if data is None: @@ -410,7 +407,7 @@ def decode_function_input(self, data): names = get_abi_input_names(func.abi) types = get_abi_input_types(func.abi) - decoded = decode_abi(types, params) + decoded = self.web3.codec.decode_abi(types, params) normalized = map_abi_data(BASE_RETURN_NORMALIZERS, types, decoded) return func, dict(zip(names, normalized)) @@ -418,7 +415,7 @@ def decode_function_input(self, data): @combomethod def find_functions_by_args(self, *args): def callable_check(fn_abi): - return check_if_arguments_can_be_encoded(fn_abi, args=args, kwargs={}) + return check_if_arguments_can_be_encoded(fn_abi, self.web3.codec, args=args, kwargs={}) return find_functions_by_identifier( self.abi, self.web3, self.address, callable_check @@ -454,6 +451,7 @@ def _prepare_transaction(cls, @classmethod def _find_matching_fn_abi(cls, fn_identifier=None, args=None, kwargs=None): return find_matching_fn_abi(cls.abi, + cls.web3.codec, fn_identifier=fn_identifier, args=args, kwargs=kwargs) @@ -685,6 +683,7 @@ def _none_addr(datatype, data): class ImplicitMethod(ConciseMethod): def __call_by_default(self, args): function_abi = find_matching_fn_abi(self._function.contract_abi, + self._function.web3.codec, fn_identifier=self._function.function_identifier, args=args) @@ -764,6 +763,7 @@ def _set_function_info(self): if not self.abi: self.abi = find_matching_fn_abi( self.contract_abi, + self.web3.codec, self.function_identifier, self.args, self.kwargs @@ -1014,7 +1014,7 @@ def _parse_logs(self, txn_receipt, errors): for log in txn_receipt['logs']: try: - rich_log = get_event_data(self.abi, log) + rich_log = get_event_data(self.web3.codec, self.abi, log) except (MismatchedABI, LogTopicError, InvalidEventABI, TypeError) as e: if errors == DISCARD: continue @@ -1035,7 +1035,7 @@ def _parse_logs(self, txn_receipt, errors): @combomethod def processLog(self, log): - return get_event_data(self.abi, log) + return get_event_data(self.web3.codec, self.abi, log) @combomethod def createFilter( @@ -1063,6 +1063,7 @@ def createFilter( _, event_filter_params = construct_event_filter_params( self._get_event_abi(), + self.web3.codec, contract_address=self.address, argument_filters=_filters, fromBlock=fromBlock, @@ -1071,7 +1072,7 @@ def createFilter( topics=topics, ) - filter_builder = EventFilterBuilder(event_abi) + filter_builder = EventFilterBuilder(event_abi, self.web3.codec) filter_builder.address = event_filter_params.get('address') filter_builder.fromBlock = event_filter_params.get('fromBlock') filter_builder.toBlock = event_filter_params.get('toBlock') @@ -1090,7 +1091,7 @@ def createFilter( filter_builder.args[arg].match_single(value) log_filter = filter_builder.deploy(self.web3) - log_filter.log_entry_formatter = get_event_data(self._get_event_abi()) + log_filter.log_entry_formatter = get_event_data(self.web3.codec, self._get_event_abi()) log_filter.builder = filter_builder return log_filter @@ -1099,7 +1100,8 @@ def createFilter( def build_filter(self): builder = EventFilterBuilder( self._get_event_abi(), - formatter=get_event_data(self._get_event_abi())) + self.web3.codec, + formatter=get_event_data(self.web3.codec, self._get_event_abi())) builder.address = self.address return builder @@ -1186,6 +1188,7 @@ def getLogs(self, # Namely, convert event names to their keccak signatures data_filter_set, event_filter_params = construct_event_filter_params( abi, + self.web3.codec, contract_address=self.address, argument_filters=_filters, fromBlock=fromBlock, @@ -1200,7 +1203,7 @@ def getLogs(self, logs = self.web3.eth.getLogs(event_filter_params) # Convert raw binary data to Python proxy objects as described by ABI - return tuple(get_event_data(abi, entry) for entry in logs) + return tuple(get_event_data(self.web3.codec, abi, entry) for entry in logs) @classmethod def factory(cls, class_name, **kwargs): @@ -1346,12 +1349,12 @@ def call_contract_function( return_data = web3.eth.call(call_transaction, block_identifier=block_id) if fn_abi is None: - fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs) + fn_abi = find_matching_fn_abi(contract_abi, web3.codec, function_identifier, args, kwargs) output_types = get_abi_output_types(fn_abi) try: - output_data = decode_abi(output_types, return_data) + output_data = web3.codec.decode_abi(output_types, return_data) except DecodingError as e: # Provide a more helpful error message than the one provided by # eth-abi-utils diff --git a/web3/main.py b/web3/main.py index 1f73334a37..2e30befd9e 100644 --- a/web3/main.py +++ b/web3/main.py @@ -1,3 +1,6 @@ +from eth_abi.codec import ( + ABICodec, +) from eth_utils import ( add_0x_prefix, apply_to_return_value, @@ -15,6 +18,8 @@ from ens import ENS from web3._utils.abi import ( + build_default_registry, + build_strict_registry, map_abi_data, ) from web3._utils.decorators import ( @@ -140,6 +145,8 @@ def __init__(self, provider=None, middlewares=None, modules=None, ens=empty): attach_modules(self, modules) + self.codec = ABICodec(build_default_registry()) + self.ens = ens @property @@ -219,6 +226,9 @@ def solidityKeccak(cls, abi_types, values): def isConnected(self): return self.provider.isConnected() + def is_encodable(self, _type, value): + return self.codec.is_encodable(_type, value) + @property def ens(self): if self._ens is empty: @@ -245,3 +255,6 @@ def enable_unstable_package_management_api(self): from web3.pm import PM if not hasattr(self, '_pm'): PM.attach(self, '_pm') + + def enable_strict_bytes_type_checking(self): + self.codec = ABICodec(build_strict_registry()) From 3b83926aaf154a017403402a55f74f44484688e9 Mon Sep 17 00:00:00 2001 From: Keri Date: Thu, 12 Sep 2019 15:14:58 -0600 Subject: [PATCH 2/2] Change to compositional instead of OO --- conftest.py | 2 +- docs/abi_types.rst | 4 + docs/contracts.rst | 134 +++++----- docs/examples.rst | 36 +-- docs/overview.rst | 32 +++ tests/core/contracts/conftest.py | 26 +- .../contracts/test_contract_call_interface.py | 4 +- .../test_contract_constructor_encoding.py | 28 ++- .../contracts/test_contract_deployment.py | 16 +- .../test_contract_method_abi_encoding.py | 122 ++++------ ...st_contract_method_to_argument_matching.py | 8 +- .../contracts/test_extracting_event_data.py | 143 +++-------- tests/core/filtering/test_utils_functions.py | 229 ++++++++++++++---- tests/core/shh-module/test_shh_post.py | 34 +-- tests/core/utilities/test_abi_is_encodable.py | 39 ++- .../test_construct_event_data_set.py | 35 +-- .../utilities/test_construct_event_topics.py | 42 +--- .../utilities/test_event_filter_builder.py | 31 +++ tests/core/utilities/test_validation.py | 2 - web3/_utils/abi.py | 100 +++++--- web3/_utils/filters.py | 1 + 21 files changed, 574 insertions(+), 494 deletions(-) diff --git a/conftest.py b/conftest.py index ba98c6459a..69373ff634 100644 --- a/conftest.py +++ b/conftest.py @@ -103,7 +103,7 @@ def web3(): @pytest.fixture -def web3_strict_types(): +def w3_strict_abi(): w3 = Web3(EthereumTesterProvider()) w3.enable_strict_bytes_type_checking() return w3 diff --git a/docs/abi_types.rst b/docs/abi_types.rst index 03576709ac..4d7172c8a9 100644 --- a/docs/abi_types.rst +++ b/docs/abi_types.rst @@ -28,6 +28,10 @@ All addresses must be supplied in one of three ways: Strict Bytes Type Checking -------------------------- +.. note :: + + In version 6, this will be the default behavior + There is a method on web3 that will enable stricter bytes type checking. The default is to allow Python strings, and to allow bytestrings less than the specified byte size. To enable stricter checks, use diff --git a/docs/contracts.rst b/docs/contracts.rst index 1efc48cac8..e94a1fc051 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -545,12 +545,12 @@ The following values will raise an error by default: - String is not valid hex However, you may want to be stricter with acceptable values for bytes types. -For this you can use the ``enable_strict_bytes_type_checking`` method, +For this you can use the :meth:`w3.enable_strict_bytes_type_checking()` method, which is available on the web3 instance. A web3 instance which has had this method invoked will enforce a stricter set of rules on which values are accepted. - A Python string that is not prefixed with ``0x`` will throw an error. - - A bytestring that is less than (or greater than) the specified byte size + - A bytestring whose length not exactly the specified byte size will raise an error. .. list-table:: Valid byte and hex strings for a strict bytes4 type @@ -586,13 +586,75 @@ invoked will enforce a stricter set of rules on which values are accepted. - Needs to have exactly 4 bytes -For example, the following contract code will generate the abi below and some bytecode: +Taking the following contract code as an example: .. testsetup:: from web3 import Web3 w3 = Web3(Web3.EthereumTesterProvider()) bytecode = "608060405234801561001057600080fd5b506040516106103803806106108339810180604052602081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185602082028301116401000000008211171561007e57600080fd5b5050929190505050806000908051906020019061009c9291906100a3565b505061019c565b82805482825590600052602060002090600f0160109004810192821561015a5791602002820160005b8382111561012a57835183826101000a81548161ffff02191690837e010000000000000000000000000000000000000000000000000000000000009004021790555092602001926002016020816001010492830192600103026100cc565b80156101585782816101000a81549061ffff021916905560020160208160010104928301926001030261012a565b505b509050610167919061016b565b5090565b61019991905b8082111561019557600081816101000a81549061ffff021916905550600101610171565b5090565b90565b610465806101ab6000396000f3fe608060405260043610610051576000357c0100000000000000000000000000000000000000000000000000000000900480633b3230ee14610056578063d7c8a410146100e7578063dfe3136814610153575b600080fd5b34801561006257600080fd5b5061008f6004803603602081101561007957600080fd5b8101908080359060200190929190505050610218565b60405180827dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b3480156100f357600080fd5b506100fc61026c565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561013f578082015181840152602081019050610124565b505050509050019250505060405180910390f35b34801561015f57600080fd5b506102166004803603602081101561017657600080fd5b810190808035906020019064010000000081111561019357600080fd5b8201836020820111156101a557600080fd5b803590602001918460208302840111640100000000831117156101c757600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050610326565b005b60008181548110151561022757fe5b9060005260206000209060109182820401919006600202915054906101000a90047e010000000000000000000000000000000000000000000000000000000000000281565b6060600080548060200260200160405190810160405280929190818152602001828054801561031c57602002820191906000526020600020906000905b82829054906101000a90047e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190600201906020826001010492830192600103820291508084116102a95790505b5050505050905090565b806000908051906020019061033c929190610340565b5050565b82805482825590600052602060002090600f016010900481019282156103f75791602002820160005b838211156103c757835183826101000a81548161ffff02191690837e01000000000000000000000000000000000000000000000000000000000000900402179055509260200192600201602081600101049283019260010302610369565b80156103f55782816101000a81549061ffff02191690556002016020816001010492830192600103026103c7565b505b5090506104049190610408565b5090565b61043691905b8082111561043257600081816101000a81549061ffff02191690555060010161040e565b5090565b9056fea165627a7a72305820a8f9f1f4815c1eedfb8df31298a5cd13b198895de878871328b5d96296b69b4e0029" + abi = ''' + [ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "bytes2Value", + "outputs": [ + { + "name": "", + "type": "bytes2" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBytes2Value", + "outputs": [ + { + "name": "", + "type": "bytes2[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_bytes2Value", + "type": "bytes2[]" + } + ], + "name": "setBytes2Value", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_bytes2Value", + "type": "bytes2[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } + ] + '''.strip() .. code-block:: @@ -614,72 +676,12 @@ For example, the following contract code will generate the abi below and some by ... # } ... # } -.. doctest:: - >>> abi = ''' - ... [ - ... { - ... "constant": true, - ... "inputs": [ - ... { - ... "name": "", - ... "type": "uint256" - ... } - ... ], - ... "name": "bytes2Value", - ... "outputs": [ - ... { - ... "name": "", - ... "type": "bytes2" - ... } - ... ], - ... "payable": false, - ... "stateMutability": "view", - ... "type": "function" - ... }, - ... { - ... "constant": true, - ... "inputs": [], - ... "name": "getBytes2Value", - ... "outputs": [ - ... { - ... "name": "", - ... "type": "bytes2[]" - ... } - ... ], - ... "payable": false, - ... "stateMutability": "view", - ... "type": "function" - ... }, - ... { - ... "constant": false, - ... "inputs": [ - ... { - ... "name": "_bytes2Value", - ... "type": "bytes2[]" - ... } - ... ], - ... "name": "setBytes2Value", - ... "outputs": [], - ... "payable": false, - ... "stateMutability": "nonpayable", - ... "type": "function" - ... }, - ... { - ... "inputs": [ - ... { - ... "name": "_bytes2Value", - ... "type": "bytes2[]" - ... } - ... ], - ... "payable": false, - ... "stateMutability": "nonpayable", - ... "type": "constructor" - ... } - ... ] - ... '''.strip() + >>> # abi = "..." >>> # bytecode = "6080..." +.. doctest:: + >>> ArraysContract = w3.eth.contract(abi=abi, bytecode=bytecode) >>> tx_hash = ArraysContract.constructor([b'b']).transact() diff --git a/docs/examples.rst b/docs/examples.rst index 3cc2fde727..dde92106ca 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -202,19 +202,19 @@ Given the following solidity source file stored at ``contract.sol``. .. code-block:: javascript contract StoreVar { - + uint8 public _myVar; event MyEvent(uint indexed _var); - + function setVar(uint8 _var) public { _myVar = _var; MyEvent(_var); } - + function getVar() public view returns (uint8) { return _myVar; } - + } The following example demonstrates a few things: @@ -229,45 +229,45 @@ The following example demonstrates a few things: import sys import time import pprint - + from web3.providers.eth_tester import EthereumTesterProvider from web3 import Web3 from solc import compile_source - + def compile_source_file(file_path): with open(file_path, 'r') as f: source = f.read() - + return compile_source(source) - - + + def deploy_contract(w3, contract_interface): tx_hash = w3.eth.contract( abi=contract_interface['abi'], bytecode=contract_interface['bin']).deploy() - + address = w3.eth.getTransactionReceipt(tx_hash)['contractAddress'] return address - - + + w3 = Web3(EthereumTesterProvider()) - + contract_source_path = 'contract.sol' compiled_sol = compile_source_file('contract.sol') - + contract_id, contract_interface = compiled_sol.popitem() - + address = deploy_contract(w3, contract_interface) print("Deployed {0} to: {1}\n".format(contract_id, address)) - + store_var_contract = w3.eth.contract( address=address, abi=contract_interface['abi']) - + gas_estimate = store_var_contract.functions.setVar(255).estimateGas() print("Gas estimate to transact with setVar: {0}\n".format(gas_estimate)) - + if gas_estimate < 100000: print("Sending transaction to setVar(255)\n") tx_hash = store_var_contract.functions.setVar(255).transact() diff --git a/docs/overview.rst b/docs/overview.rst index fb6673bc0e..67b80d14c8 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -341,6 +341,38 @@ Cryptographic Hashing >>> Web3.soliditySha3(['address'], ["ethereumfoundation.eth"]) HexBytes("0x913c99ea930c78868f1535d34cd705ab85929b2eaaf70fcd09677ecd6e5d75e9") +Check Encodability +~~~~~~~~~~~~~~~~~~~~ + +.. py:method:: w3.is_encodable(_type, value) + + Returns ``True`` if a value can be encoded as the given type. Otherwise returns ``False``. + + .. code-block:: python + + >>> from web3.auto.gethdev import w3 + >>> w3.is_encodable('bytes2', b'12') + True + >>> w3.is_encodable('bytes2', b'1') + True + >>> w3.is_encodable('bytes2', '0x1234') + True + >>> w3.is_encodable('bytes2', b'123') + False + +.. py:method:: w3.enable_strict_bytes_type_checking() + + Enables stricter bytes type checking. For more examples see :ref:`enable-strict-byte-check` + + .. doctest:: + + >>> from web3.auto.gethdev import w3 + >>> w3.enable_strict_bytes_type_checking() + >>> w3.is_encodable('bytes2', b'12') + True + >>> w3.is_encodable('bytes2', b'1') + False + Modules ------- diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 36617113f1..717906880b 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -215,11 +215,11 @@ def WithConstructorArgumentsContract(web3, @pytest.fixture() -def WithConstructorArgumentsContractStrict(web3_strict_types, +def WithConstructorArgumentsContractStrict(w3_strict_abi, WITH_CONSTRUCTOR_ARGUMENTS_CODE, WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, WITH_CONSTRUCTOR_ARGUMENTS_ABI): - return web3_strict_types.eth.contract( + return w3_strict_abi.eth.contract( abi=WITH_CONSTRUCTOR_ARGUMENTS_ABI, bytecode=WITH_CONSTRUCTOR_ARGUMENTS_CODE, bytecode_runtime=WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, @@ -402,27 +402,27 @@ def EMITTER(EMITTER_CODE, @pytest.fixture() -def StrictEmitter(web3_strict_types, EMITTER): - web3 = web3_strict_types - return web3.eth.contract(**EMITTER) +def StrictEmitter(w3_strict_abi, EMITTER): + w3 = w3_strict_abi + return w3.eth.contract(**EMITTER) @pytest.fixture() -def strict_emitter(web3_strict_types, +def strict_emitter(w3_strict_abi, StrictEmitter, wait_for_transaction, wait_for_block, address_conversion_func): - web3 = web3_strict_types + w3 = w3_strict_abi - wait_for_block(web3) + wait_for_block(w3) deploy_txn_hash = StrictEmitter.constructor().transact( - {'from': web3.eth.coinbase, 'gas': 1000000} + {'from': w3.eth.coinbase, 'gas': 1000000} ) - deploy_receipt = wait_for_transaction(web3, deploy_txn_hash) + deploy_receipt = wait_for_transaction(w3, deploy_txn_hash) contract_address = address_conversion_func(deploy_receipt['contractAddress']) - bytecode = web3.eth.getCode(contract_address) + bytecode = w3.eth.getCode(contract_address) assert bytecode == StrictEmitter.bytecode_runtime emitter_contract = StrictEmitter(address=contract_address) assert emitter_contract.address == contract_address @@ -625,8 +625,8 @@ def ArraysContract(web3, ARRAYS_CONTRACT): @pytest.fixture() -def StrictArraysContract(web3_strict_types, ARRAYS_CONTRACT): - return web3_strict_types.eth.contract(**ARRAYS_CONTRACT) +def StrictArraysContract(w3_strict_abi, ARRAYS_CONTRACT): + return w3_strict_abi.eth.contract(**ARRAYS_CONTRACT) CONTRACT_PAYABLE_TESTER_SOURCE = """ diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index f17ff8851e..221a272577 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -75,7 +75,7 @@ def arrays_contract(web3, ArraysContract, address_conversion_func): @pytest.fixture() -def strict_arrays_contract(web3_strict_types, StrictArraysContract, address_conversion_func): +def strict_arrays_contract(w3_strict_abi, StrictArraysContract, address_conversion_func): # bytes_32 = [keccak('0'), keccak('1')] bytes32_array = [ b'\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m', # noqa: E501 @@ -83,7 +83,7 @@ def strict_arrays_contract(web3_strict_types, StrictArraysContract, address_conv ] byte_arr = [b'\xff', b'\xff', b'\xff', b'\xff'] return deploy( - web3_strict_types, + w3_strict_abi, StrictArraysContract, address_conversion_func, args=[bytes32_array, byte_arr] diff --git a/tests/core/contracts/test_contract_constructor_encoding.py b/tests/core/contracts/test_contract_constructor_encoding.py index 8ed8f264e5..3711dfaf9a 100644 --- a/tests/core/contracts/test_contract_constructor_encoding.py +++ b/tests/core/contracts/test_contract_constructor_encoding.py @@ -64,7 +64,7 @@ def test_contract_constructor_encoding_encoding(web3, WithConstructorArgumentsCo def test_contract_constructor_encoding_encoding_warning(web3, WithConstructorArgumentsContract): with pytest.warns( DeprecationWarning, - match="in v6 it will be invalid to pass a hex string without the \"0x\" prefix" + match='in v6 it will be invalid to pass a hex string without the \"0x\" prefix' ): deploy_data = WithConstructorArgumentsContract._encode_constructor_data([1234, '61626364']) encoded_args = '0x00000000000000000000000000000000000000000000000000000000000004d26162636400000000000000000000000000000000000000000000000000000000' # noqa: E501 @@ -76,6 +76,28 @@ def test_contract_constructor_encoding_encoding_warning(web3, WithConstructorArg assert deploy_data.endswith(remove_0x_prefix(expected_ending)) +@pytest.mark.parametrize( + 'bytes_arg,encoded_args', + ( + ('0x' + '00' * 32, '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000000'), # noqa: E501 + (b'1' * 32, '0x00000000000000000000000000000000000000000000000000000000000004d23131313131313131313131313131313131313131313131313131313131313131'), # noqa: E501 + ), +) +def test_contract_constructor_encoding_encoding_strict( + w3_strict_abi, + WithConstructorArgumentsContractStrict, + encoded_args, + bytes_arg): + + deploy_data = WithConstructorArgumentsContractStrict._encode_constructor_data([1234, bytes_arg]) + + expected_ending = encode_hex( + w3_strict_abi.codec.encode_abi(['uint256', 'bytes32'], [1234, bytes_arg]) + ) + assert expected_ending == encoded_args + assert deploy_data.endswith(remove_0x_prefix(expected_ending)) + + @pytest.mark.parametrize( 'bytes_arg', ( @@ -85,8 +107,8 @@ def test_contract_constructor_encoding_encoding_warning(web3, WithConstructorArg '61626364', ), ) -def test_contract_constructor_encoding_encoding_strict( - web3_strict_types, +def test_contract_constructor_encoding_encoding_strict_errors( + w3_strict_abi, WithConstructorArgumentsContractStrict, bytes_arg): with pytest.raises( diff --git a/tests/core/contracts/test_contract_deployment.py b/tests/core/contracts/test_contract_deployment.py index dbc62f2ab7..da7767c64c 100644 --- a/tests/core/contracts/test_contract_deployment.py +++ b/tests/core/contracts/test_contract_deployment.py @@ -4,9 +4,6 @@ from eth_utils import ( decode_hex, ) -from web3.exceptions import ( - ValidationError, -) def test_contract_deployment_no_constructor(web3, MathContract, @@ -62,25 +59,25 @@ def test_contract_deployment_with_constructor_with_arguments(web3, '0x0000000000000000000000000000000000000000000000000000000000000000' ) ) -def test_contract_deployment_with_constructor_with_arguments_strict(web3_strict_types, +def test_contract_deployment_with_constructor_with_arguments_strict(w3_strict_abi, WithConstructorArgumentsContractStrict, # noqa: E501 WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME, # noqa: E501 constructor_arg): deploy_txn = WithConstructorArgumentsContractStrict.constructor( - 1234, constructor_arg - ).transact() + 1234, constructor_arg + ).transact() - txn_receipt = web3_strict_types.eth.waitForTransactionReceipt(deploy_txn) + txn_receipt = w3_strict_abi.eth.waitForTransactionReceipt(deploy_txn) assert txn_receipt is not None assert txn_receipt['contractAddress'] contract_address = txn_receipt['contractAddress'] - blockchain_code = web3_strict_types.eth.getCode(contract_address) + blockchain_code = w3_strict_abi.eth.getCode(contract_address) assert blockchain_code == decode_hex(WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME) -def test_contract_deployment_with_constructor_with_arguments_strict_error(web3_strict_types, +def test_contract_deployment_with_constructor_with_arguments_strict_error(w3_strict_abi, WithConstructorArgumentsContractStrict, # noqa: E501 WITH_CONSTRUCTOR_ARGUMENTS_RUNTIME): # noqa: E501 with pytest.raises( @@ -89,6 +86,7 @@ def test_contract_deployment_with_constructor_with_arguments_strict_error(web3_s ): WithConstructorArgumentsContractStrict.constructor(1234, 'abcd').transact() + def test_contract_deployment_with_constructor_with_address_argument(web3, WithConstructorAddressArgumentsContract, # noqa: E501 WITH_CONSTRUCTOR_ADDRESS_RUNTIME): # noqa: E501 diff --git a/tests/core/contracts/test_contract_method_abi_encoding.py b/tests/core/contracts/test_contract_method_abi_encoding.py index 1e2772e95d..c5e4540e93 100644 --- a/tests/core/contracts/test_contract_method_abi_encoding.py +++ b/tests/core/contracts/test_contract_method_abi_encoding.py @@ -12,50 +12,50 @@ @pytest.mark.parametrize( - 'abi,method,arguments,data,expected', + 'abi,arguments,data,expected', ( - (ABI_A, 'a', [], None, '0x0dbe671f'), - (ABI_A, 'a', [], '0x12345678', '0x12345678'), - ( + pytest.param(ABI_A, [], None, '0x0dbe671f', id="ABI_A, no args, no data"), + pytest.param(ABI_A, [], '0x12345678', '0x12345678', id="ABI_A, no args, some data"), + pytest.param( ABI_B, - 'a', [0], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000000', + id='ABI_B, valid int args, no data' ), - ( + pytest.param( ABI_B, - 'a', [1], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + id='ABI_B, valid int args, no data' ), - ( + pytest.param( ABI_C, - 'a', [1], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + id='ABI_B, valid int args, no data' ), - ( + pytest.param( ABI_C, - 'a', [b'a'], None, '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000', + id='ABI_C, valid byte args, no data' ), - ( + pytest.param( ABI_C, - 'a', ['0x61'], None, '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000', + id='ABI_C, valid hex args, no data' ), ), ) -def test_contract_abi_encoding(web3, abi, method, arguments, data, expected): +def test_contract_abi_encoding(web3, abi, arguments, data, expected): contract = web3.eth.contract(abi=abi) - actual = contract.encodeABI(method, arguments, data=data) + actual = contract.encodeABI('a', arguments, data=data) assert actual == expected @@ -71,100 +71,68 @@ def test_contract_abi_encoding_warning(web3): assert actual == '0x9f3fab586100000000000000000000000000000000000000000000000000000000000000' # noqa: E501 -@pytest.mark.parametrize( - 'abi,method,kwargs,expected', - ( - ( - ABI_D, - 'byte_array', - { - 'b': [ - '0x5595c210956e7721f9b692e702708556aa9aabb14ea163e96afa56ffbe9fa809', - '0x6f8d2fa18448afbfe4f82143c384484ad09a0271f3a3c0eb9f629e703f883125', - ], - }, - '0xf166d6f8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025595c210956e7721f9b692e702708556aa9aabb14ea163e96afa56ffbe9fa8096f8d2fa18448afbfe4f82143c384484ad09a0271f3a3c0eb9f629e703f883125', # noqa: E501 - ), - ), -) -def test_contract_abi_encoding_kwargs(web3, abi, method, kwargs, expected): - contract = web3.eth.contract(abi=abi) - actual = contract.encodeABI(method, kwargs=kwargs) - assert actual == expected +def test_contract_abi_encoding_kwargs(web3): + contract = web3.eth.contract(abi=ABI_D) + kwargs = { + 'b': [ + '0x5595c210956e7721f9b692e702708556aa9aabb14ea163e96afa56ffbe9fa809', + '0x6f8d2fa18448afbfe4f82143c384484ad09a0271f3a3c0eb9f629e703f883125', + ], + } + actual = contract.encodeABI('byte_array', kwargs=kwargs) + assert actual == '0xf166d6f8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025595c210956e7721f9b692e702708556aa9aabb14ea163e96afa56ffbe9fa8096f8d2fa18448afbfe4f82143c384484ad09a0271f3a3c0eb9f629e703f883125' # noqa: E501 -@pytest.mark.parametrize( - 'abi,method,arguments,data', - ( - ( - ABI_C, - 'a', - [b'a'], - None, - ), - ( - ABI_C, - 'a', - ['0x61'], - None, - ), - ( - ABI_C, - 'a', - ['61'], - None, - ), - ), -) -def test_contract_abi_encoding_strict_with_error(web3_strict_types, abi, method, arguments, data): - contract = web3_strict_types.eth.contract(abi=abi) +@pytest.mark.parametrize('arguments', ([b'a'], ['0x61'], ['61'],)) +def test_contract_abi_encoding_strict_with_error(w3_strict_abi, arguments): + contract = w3_strict_abi.eth.contract(abi=ABI_C) with pytest.raises(ValidationError): - contract.encodeABI(method, arguments, data=data) + contract.encodeABI('a', arguments, data=None) @pytest.mark.parametrize( - 'abi,method,arguments,data,expected', + 'abi,arguments,data,expected', ( - (ABI_A, 'a', [], None, '0x0dbe671f'), - (ABI_A, 'a', [], '0x12345678', '0x12345678'), - ( + pytest.param(ABI_A, [], None, '0x0dbe671f', id="ABI_A, no args, no data"), + pytest.param(ABI_A, [], '0x12345678', '0x12345678', id="ABI_A, no args, some data"), + pytest.param( ABI_B, - 'a', [0], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000000', + id='ABI_B, valid int args, no data' ), - ( + pytest.param( ABI_B, - 'a', [1], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + id='ABI_B, valid int args, no data' ), - ( + pytest.param( ABI_C, - 'a', [1], None, '0xf0fdf8340000000000000000000000000000000000000000000000000000000000000001', + id='ABI_C, valid int args, no data' ), - ( + pytest.param( ABI_C, - 'a', [b'00000000000000000000000000000000'], None, '0x9f3fab583030303030303030303030303030303030303030303030303030303030303030', + id='ABI_C, valid bytestring args, no data' ), - ( + pytest.param( ABI_C, - 'a', ['0x0000000000000000000000000000000000000000000000000000000000000000'], None, '0x9f3fab580000000000000000000000000000000000000000000000000000000000000000', + id='ABI_C, valid hexstring args, no data' ), ), ) -def test_contract_abi_encoding_strict(web3_strict_types, abi, method, arguments, data, expected): - contract = web3_strict_types.eth.contract(abi=abi) - actual = contract.encodeABI(method, arguments, data=data) +def test_contract_abi_encoding_strict(w3_strict_abi, abi, arguments, data, expected): + contract = w3_strict_abi.eth.contract(abi=abi) + actual = contract.encodeABI('a', arguments, data=data) assert actual == expected diff --git a/tests/core/contracts/test_contract_method_to_argument_matching.py b/tests/core/contracts/test_contract_method_to_argument_matching.py index d0e2325f6c..353157aa43 100644 --- a/tests/core/contracts/test_contract_method_to_argument_matching.py +++ b/tests/core/contracts/test_contract_method_to_argument_matching.py @@ -162,8 +162,8 @@ def test_error_when_duplicate_match(web3): @pytest.mark.parametrize('arguments', (['0xf00b47'], [b''], [''], ['00' * 16])) -def test_strict_errors_if_type_is_wrong(web3_strict_types, arguments): - Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS) +def test_strict_errors_if_type_is_wrong(w3_strict_abi, arguments): + Contract = w3_strict_abi.eth.contract(abi=MULTIPLE_FUNCTIONS) with pytest.raises(ValidationError): Contract._find_matching_fn_abi('a', arguments) @@ -178,8 +178,8 @@ def test_strict_errors_if_type_is_wrong(web3_strict_types, arguments): ([[(-1, True), (2, False)]], ['(int256,bool)[]']), ) ) -def test_strict_finds_function_with_matching_args(web3_strict_types, arguments, expected_types): - Contract = web3_strict_types.eth.contract(abi=MULTIPLE_FUNCTIONS) +def test_strict_finds_function_with_matching_args(w3_strict_abi, arguments, expected_types): + Contract = w3_strict_abi.eth.contract(abi=MULTIPLE_FUNCTIONS) abi = Contract._find_matching_fn_abi('a', arguments) assert abi['name'] == 'a' diff --git a/tests/core/contracts/test_extracting_event_data.py b/tests/core/contracts/test_extracting_event_data.py index a82eeeec57..fd1a8de286 100644 --- a/tests/core/contracts/test_extracting_event_data.py +++ b/tests/core/contracts/test_extracting_event_data.py @@ -42,33 +42,6 @@ def emitter(web3, Emitter, wait_for_transaction, wait_for_block, address_convers return _emitter -@pytest.fixture() -def StrictEmitter(web3_strict_types, EMITTER): - return web3_strict_types.eth.contract(**EMITTER) - - -@pytest.fixture() -def strict_emitter( - web3_strict_types, - StrictEmitter, - wait_for_transaction, - wait_for_block, - address_conversion_func): - - wait_for_block(web3_strict_types) - deploy_txn_hash = StrictEmitter.constructor().transact( - {'from': web3_strict_types.eth.coinbase, 'gas': 1000000} - ) - deploy_receipt = web3_strict_types.eth.waitForTransactionReceipt(deploy_txn_hash) - contract_address = address_conversion_func(deploy_receipt['contractAddress']) - - bytecode = web3_strict_types.eth.getCode(contract_address) - assert bytecode == StrictEmitter.bytecode_runtime - _emitter = StrictEmitter(address=contract_address) - assert _emitter.address == contract_address - return _emitter - - @pytest.fixture() def EventContract(web3, EVENT_CONTRACT): return web3.eth.contract(**EVENT_CONTRACT) @@ -215,11 +188,9 @@ def test_event_data_extraction(web3, @pytest.mark.parametrize( - 'contract_fn,event_name,call_args,expected_args', + 'call_args,expected_args', ( ( - 'logListArgs', - 'LogListArgs', [[b'13'], [b'54']], { 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 @@ -227,8 +198,6 @@ def test_event_data_extraction(web3, } ), ( - 'logListArgs', - 'LogListArgs', [[b'1'], [b'5']], { 'arg0': b' F=9\n\x03\xb6\xe1\x00\xc5\xb7\xce\xf5\xa5\xac\x08\x08\xb8\xaf\xc4d=\xdb\xda\xf1\x05|a\x0f.\xa1!', # noqa: E501 @@ -241,17 +210,16 @@ def test_event_data_extraction_bytes(web3, wait_for_transaction, emitter_log_topics, emitter_event_ids, - contract_fn, - event_name, call_args, expected_args): - emitter_fn = emitter.functions[contract_fn] + emitter_fn = emitter.functions.logListArgs txn_hash = emitter_fn(*call_args).transact() txn_receipt = wait_for_transaction(web3, txn_hash) assert len(txn_receipt['logs']) == 1 log_entry = txn_receipt['logs'][0] + event_name = 'LogListArgs' event_abi = emitter._find_matching_event_abi(event_name) event_topic = getattr(emitter_log_topics, event_name) @@ -268,40 +236,21 @@ def test_event_data_extraction_bytes(web3, assert event_data['event'] == event_name -@pytest.mark.parametrize( - 'contract_fn,event_name,call_args,expected_args', - ( - ( - 'logListArgs', - 'LogListArgs', - [['13'], ['54']], - { - 'arg0': b']\x0b\xf6sp\xbe\xa2L\xa9is\xe4\xab\xb7\xfa+nVJpgt\xa7\x8f:\xa4\x9f\xdb\x93\xf0\x8f\xae', # noqa: E501 - 'arg1': [b'T\x00'] - } - ), - ) -) def test_event_data_extraction_bytes_with_warning(web3, emitter, wait_for_transaction, - emitter_log_topics, - emitter_event_ids, - contract_fn, - event_name, - call_args, - expected_args): - emitter_fn = emitter.functions[contract_fn] + emitter_log_topics): with pytest.warns( DeprecationWarning, match='in v6 it will be invalid to pass a hex string without the "0x" prefix' ): - txn_hash = emitter_fn(*call_args).transact() + txn_hash = emitter.functions.logListArgs(['13'], ['54']).transact() txn_receipt = wait_for_transaction(web3, txn_hash) assert len(txn_receipt['logs']) == 1 log_entry = txn_receipt['logs'][0] + event_name = 'LogListArgs' event_abi = emitter._find_matching_event_abi(event_name) event_topic = getattr(emitter_log_topics, event_name) @@ -309,6 +258,10 @@ def test_event_data_extraction_bytes_with_warning(web3, assert event_topic in log_entry['topics'] event_data = get_event_data(web3.codec, event_abi, log_entry) + expected_args = { + 'arg0': b']\x0b\xf6sp\xbe\xa2L\xa9is\xe4\xab\xb7\xfa+nVJpgt\xa7\x8f:\xa4\x9f\xdb\x93\xf0\x8f\xae', # noqa: E501 + 'arg1': [b'T\x00'] + } assert event_data['args'] == expected_args assert event_data['blockHash'] == txn_receipt['blockHash'] @@ -319,33 +272,20 @@ def test_event_data_extraction_bytes_with_warning(web3, @pytest.mark.parametrize( - 'contract_fn,event_name,call_args,expected_error', + 'call_args', ( ( - 'logListArgs', - 'LogListArgs', [[b'1312'], [b'4354']], - ValidationError, ), ( - 'logListArgs', - 'LogListArgs', [[b'1'], [b'5']], - ValidationError, ), ) ) -def test_event_data_extraction_bytes_strict_with_errors(web3_strict_types, - strict_emitter, - wait_for_transaction, - emitter_log_topics, - emitter_event_ids, - contract_fn, - event_name, - call_args, - expected_error): - emitter_fn = strict_emitter.functions[contract_fn] - with pytest.raises(expected_error): +def test_event_data_extraction_bytes_strict_with_errors(strict_emitter, + call_args): + emitter_fn = strict_emitter.functions.logListArgs + with pytest.raises(ValidationError): emitter_fn(*call_args).transact() @@ -385,14 +325,14 @@ def test_dynamic_length_argument_extraction(web3, assert event_data['event'] == 'LogDynamicArgs' -def test_argument_extraction_strict_bytes_types(web3_strict_types, +def test_argument_extraction_strict_bytes_types(w3_strict_abi, strict_emitter, wait_for_transaction, emitter_log_topics): arg_0 = [b'12'] arg_1 = [b'12'] txn_hash = strict_emitter.functions.logListArgs(arg_0, arg_1).transact() - txn_receipt = wait_for_transaction(web3_strict_types, txn_hash) + txn_receipt = wait_for_transaction(w3_strict_abi, txn_hash) assert len(txn_receipt['logs']) == 1 log_entry = txn_receipt['logs'][0] @@ -403,12 +343,12 @@ def test_argument_extraction_strict_bytes_types(web3_strict_types, event_topic = emitter_log_topics.LogListArgs assert event_topic in log_entry['topics'] - encoded_arg_0 = web3_strict_types.codec.encode_abi(['bytes2'], arg_0) + encoded_arg_0 = w3_strict_abi.codec.encode_abi(['bytes2'], arg_0) padded_arg_0 = encoded_arg_0.ljust(32, b'\x00') - arg_0_topic = web3_strict_types.keccak(padded_arg_0) + arg_0_topic = w3_strict_abi.keccak(padded_arg_0) assert arg_0_topic in log_entry['topics'] - event_data = get_event_data(web3_strict_types.codec, event_abi, log_entry) + event_data = get_event_data(w3_strict_abi.codec, event_abi, log_entry) expected_args = { "arg0": arg_0_topic, @@ -664,47 +604,18 @@ def test_event_rich_log( assert empty_rich_log == tuple() -@pytest.mark.parametrize( - 'contract_fn,event_name,call_args,expected_args,process_receipt', - ( - ( - 'logListArgs', - 'LogListArgs', - [[b'13'], [b'54']], - { - 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 - 'arg1': [b'54'] - }, - True - ), - ( - 'logListArgs', - 'LogListArgs', - [[b'13'], [b'54']], - { - 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 - 'arg1': [b'54'] - }, - False - ), - ) -) +@pytest.mark.parametrize('process_receipt', (True, False)) def test_event_rich_log_with_byte_args( web3, emitter, emitter_event_ids, wait_for_transaction, - contract_fn, - event_name, - call_args, - process_receipt, - expected_args): + process_receipt): - emitter_fn = emitter.functions[contract_fn] - txn_hash = emitter_fn(*call_args).transact() + txn_hash = emitter.functions.logListArgs([b'13'], [b'54']).transact() txn_receipt = wait_for_transaction(web3, txn_hash) - event_instance = emitter.events[event_name]() + event_instance = emitter.events.LogListArgs() if process_receipt: processed_logs = event_instance.processReceipt(txn_receipt) @@ -715,6 +626,10 @@ def test_event_rich_log_with_byte_args( else: raise Exception('Unreachable!') + expected_args = { + 'arg0': b'H\x7f\xad\xb3\x16zAS7\xa5\x0c\xfe\xe2%T\xb7\x17\x81p\xf04~\x8d(\x93\x8e\x19\x97k\xd9"1', # noqa: E501 + 'arg1': [b'54'] + } assert rich_log['args'] == expected_args assert rich_log.args == expected_args for arg in expected_args: @@ -723,7 +638,7 @@ def test_event_rich_log_with_byte_args( assert rich_log['blockNumber'] == txn_receipt['blockNumber'] assert rich_log['transactionIndex'] == txn_receipt['transactionIndex'] assert is_same_address(rich_log['address'], emitter.address) - assert rich_log['event'] == event_name + assert rich_log['event'] == 'LogListArgs' def test_receipt_processing_with_discard_flag( diff --git a/tests/core/filtering/test_utils_functions.py b/tests/core/filtering/test_utils_functions.py index a72601e20a..142808fdef 100644 --- a/tests/core/filtering/test_utils_functions.py +++ b/tests/core/filtering/test_utils_functions.py @@ -3,6 +3,10 @@ ) import pytest +from eth_abi.exceptions import ( + ValueOutOfBounds, +) + from web3._utils.filters import ( match_fn, ) @@ -12,69 +16,114 @@ "data,expected,match_data_and_abi", ( ( - (-12345, 000, 111, Decimal(2) + Decimal(1) / Decimal(10)), - False, - ( - ("int", (-12345,)), - ("uint32", (444,)), - ("int", (565,)), - ("ufixed256x4", (Decimal(1.66660),)) + pytest.param( + (-12345, 000, 111, Decimal(2) + Decimal(1) / Decimal(10)), + False, + ( + ("int", (-12345,)), + ("uint32", (444,)), + ("int", (565,)), + ("ufixed256x4", (Decimal(1.66660),)) + ), + id='tuple with incorrect numerical return values' + ) + ), + ( + pytest.param( + (-12345, 000, 111, Decimal(2) + Decimal(1) / Decimal(10)), + True, + ( + ("int", (-12345,)), + ("uint32", None), + ("int", None), + ("ufixed256x4", None) + ), + id='tuple with correct numerical return values' ) ), ( - (-12345, 000, 111, Decimal(2) + Decimal(1) / Decimal(10)), - True, - ( - ("int", (-12345,)), - ("uint32", None), - ("int", None), - ("ufixed256x4", None) + pytest.param( + ("aye", "bee", "sea", b"\xde\xee"), + False, + ( + ("string", ("eee",)), + ("string", ("aye",)), + ("string", ("sea",)), + ("bytes", (b"\x00",)) + ), + id='tuple with incorrect string and bytes return values' ) ), ( - ("aye", "bee", "sea", b"\xde\xee"), - False, - ( - ("string", ("eee",)), - ("string", ("aye",)), - ("string", ("sea",)), - ("bytes", (b"\x00",)) + pytest.param( + ("aye", "bee", "sea", "0x1234"), + True, + ( + ("string", ("aye",)), + ("string", ("bee",)), + ("string", ("sea",)), + ("bytes", (b"\x124",)) + ), + id='tuple with valid string and hex values' ) ), ( - ("aye", "bee", "sea", b"\xde\xee"), - True, - ( - ("string", ("aye",)), - ("string", ("bee",)), - ("string", ("sea",)), - ("bytes", (b"\xde\xee",)) + pytest.param( + ("aye", "bee", "sea", b"\xde\xee"), + True, + ( + ("string", ("aye",)), + ("string", ("bee",)), + ("string", ("sea",)), + ("bytes", (b"\xde\xee",)) + ), + id="tuple with valid string and bytes values" ) ), ( - ("aye", "bee", "sea", b"\xde\xee"), - True, - ( - ("string", None), - ("string", None), - ("string", None), - ("bytes", None) + pytest.param( + ("aye", "bee", "sea", b"\xde\xee"), + True, + ( + ("string", None), + ("string", None), + ("string", None), + ("bytes", None) + ), + id="tuple with valid string and bytes values" ) ), ( - (("aye", "bee"), ("sea", "dee")), - True, - ( - ("string[]", (("aye", "bee"),)), - ("string[]", (("sea", "dee"),)), + pytest.param( + ((b"aye", b"bee"), ("sea", "dee")), + True, + ( + ("bytes3[]", ((b"aye", b"bee"),)), + ("string[]", (("sea", "dee"),)), + ), + id="lists with valud string and bytes values" ) ), ( - (["eee", "eff"], ["gee", "eich"]), - False, - ( - ("string[]", (("aye", "bee"),)), - ("string[]", (("sea", "dee"),)), + pytest.param( + (("aye", "bee"), ("sea", "dee")), + True, + ( + ("string[]", (("aye", "bee"),)), + ("string[]", (("sea", "dee"),)), + ), + id="lists with valid string values" + ) + ), + ( + pytest.param( + (["eee", "eff"], ["gee", "eich"]), + False, + ( + ("string[]", (("aye", "bee"),)), + ("string[]", (("sea", "dee"),)), + ), + id="lists with valid string values, incorrect return values" ) ), ) @@ -85,6 +134,96 @@ def test_match_fn_with_various_data_types(web3, data, expected, match_data_and_a assert match_fn(web3, match_data_and_abi, encoded_data) == expected +@pytest.mark.parametrize( + "data,expected,match_data_and_abi", + ( + ( + pytest.param( + ((b"1234",)), + True, + ( + ("bytes4", (b"1234",)), + ), + id="tuple with valid bytes value" + ) + ), + ( + pytest.param( + (("0x12343434",)), + True, + ( + ("bytes4", (b"\x12444",)), + ), + id="tuple with valid hex string" + ) + ), + ( + pytest.param( + ((b"1234",)), + False, + ( + ("bytes4", (b"5678",)), + ), + id="tuple with invalid return value" + ) + ), + ( + pytest.param( + (("0x1212", b"34"),), + True, + ( + ("bytes2[]", ((b"\x12\x12", b"34"),)), + ), + id="list with valid hexstring and byte values" + ) + ), + ( + pytest.param( + (("0x1212", b"34"),), + False, + ( + ("bytes2[]", ((b"12", b"34"),)), + ), + id="list with incorrect hexstring and byte return values" + ) + ), + ( + pytest.param( + (["aye", "bee"], ["sea", "dee"]), + True, + ( + ("string[]", (("aye", "bee"),)), + ("string[]", (("sea", "dee"),)), + ), + id="list with valid string values" + ) + ), + ) +) +def test_match_fn_with_various_data_types_strict(w3_strict_abi, + data, + expected, + match_data_and_abi): + abi_types, match_data = zip(*match_data_and_abi) + encoded_data = w3_strict_abi.codec.encode_abi(abi_types, data) + assert match_fn(w3_strict_abi, match_data_and_abi, encoded_data) == expected + + +@pytest.mark.parametrize( + "data,abi_type", + ( + pytest.param((b"124",), ("bytes4",), id="tuple with invalid bytes input"), + pytest.param(("0x123434",), ("bytes4",), id="tuple with hex input - too small"), + pytest.param(("0x1234343232",), ("bytes4",), id="tuple with hex input - too big"), + ) +) +def test_encode_abi_with_wrong_types_strict(w3_strict_abi, + data, + abi_type): + with pytest.raises(ValueOutOfBounds): + w3_strict_abi.codec.encode_abi(abi_type, data) + + def test_wrong_type_match_data(web3): data = ("hello", "goodbye") match_data_and_abi = ( diff --git a/tests/core/shh-module/test_shh_post.py b/tests/core/shh-module/test_shh_post.py index 44ad769bc1..ff69eb0c63 100644 --- a/tests/core/shh-module/test_shh_post.py +++ b/tests/core/shh-module/test_shh_post.py @@ -1,24 +1,10 @@ -# def test_shh_post(web3, skip_if_testrpc): -# skip_if_testrpc(web3) -# assert 0 -# receiver_pub = web3.shh.getPublicKey(web3.shh.newKeyPair()) -# try: -# response = web3.shh.post({ -# "topic": "0x12345678", -# "powTarget": 2.5, -# "powTime": 2, -# "payload": web3.toHex(text="testing shh on web3.py"), -# "pubKey": receiver_pub, -# }) -# except ValueError: -# # with pytest.raises(ValueError): -# response = web3.shh.post({ -# "topic": "0x12345678", -# "powTarget": 2.5, -# "powTime": 2, -# "payload": web3.toHex(text="testing shh on web3.py"), -# "pubKey": receiver_pub, -# }) -# assert False -# else: -# assert response is False +def test_shh_post(web3, skip_if_testrpc): + skip_if_testrpc(web3) + receiver_pub = web3.shh.getPublicKey(web3.shh.newKeyPair()) + assert web3.shh.post({ + "topic": "0x12345678", + "powTarget": 2.5, + "powTime": 2, + "payload": web3.toHex(text="testing shh on web3.py"), + "pubKey": receiver_pub, + }) diff --git a/tests/core/utilities/test_abi_is_encodable.py b/tests/core/utilities/test_abi_is_encodable.py index 3a78389b00..958c8e631a 100644 --- a/tests/core/utilities/test_abi_is_encodable.py +++ b/tests/core/utilities/test_abi_is_encodable.py @@ -1,10 +1,5 @@ import pytest -from web3 import ( - EthereumTesterProvider, - Web3, -) - @pytest.mark.parametrize( 'value,_type,expected', @@ -42,7 +37,6 @@ # Special bytes behavior ('0x12', 'bytes2', True), # with or without 0x OK (b'\x12', 'bytes2', True), # as bytes value undersize OK - ('0123', 'bytes1', False), # no oversize hex strings ('1', 'bytes2', False), # no odd length ('0x1', 'bytes2', False), # no odd length @@ -70,28 +64,26 @@ ((b'\x80', 0), '(string,int128)', False), ), ) -def test_is_encodable(value, _type, expected): - w3 = Web3(EthereumTesterProvider()) - actual = w3.is_encodable(_type, value) +def test_is_encodable(web3, value, _type, expected): + actual = web3.is_encodable(_type, value) assert actual is expected @pytest.mark.parametrize( 'value,_type,expected', ( - ('12', 'bytes2', True), - ('0123', 'bytes2', True), - - ('12', 'bytes', True), + ('12', 'bytes2', True), # no 0x prefix, can be decoded as hex + ('0123', 'bytes2', True), # no 0x prefix, can be decoded as hex + ('0123', 'bytes1', False), # no oversize values + ('12', 'bytes', True), # no 0x prefix, can be decoded as hex ) ) -def test_is_encodable_warnings(value, _type, expected): - w3 = Web3(EthereumTesterProvider()) +def test_is_encodable_warnings(web3, value, _type, expected): with pytest.warns( DeprecationWarning, match='in v6 it will be invalid to pass a hex string without the "0x" prefix' ): - actual = w3.is_encodable(_type, value) + actual = web3.is_encodable(_type, value) assert actual is expected @@ -108,11 +100,11 @@ def test_is_encodable_warnings(value, _type, expected): ('0x1', 'bytes2', False), # no odd length # Special bytes behavior - ('12', 'bytes', False), # has to have 0x if string + ('12', 'bytes', False), # no hex strings without leading 0x ('0x12', 'bytes', True), - ('1', 'bytes', False), - ('0x1', 'bytes', False), - ('0x0x0x0x', 'bytes', False), + ('1', 'bytes', False), # no hex strings without leading 0x + ('0x1', 'bytes', False), # cannot be decoded as hex + ('0x0x0x0x', 'bytes', False), # cannot be decoded as hex # Special string behavior (b'', 'string', True), @@ -120,9 +112,6 @@ def test_is_encodable_warnings(value, _type, expected): (b'\x80', 'string', False), # bytes that cannot be decoded with utf-8 are invalid ), ) -def test_is_encodable_strict(value, _type, expected): - w3 = Web3(EthereumTesterProvider()) - w3.enable_strict_bytes_type_checking() - - actual = w3.is_encodable(_type, value) +def test_is_encodable_strict(w3_strict_abi, value, _type, expected): + actual = w3_strict_abi.is_encodable(_type, value) assert actual is expected diff --git a/tests/core/utilities/test_construct_event_data_set.py b/tests/core/utilities/test_construct_event_data_set.py index f6f341d453..6cbc7d73fd 100644 --- a/tests/core/utilities/test_construct_event_data_set.py +++ b/tests/core/utilities/test_construct_event_data_set.py @@ -41,30 +41,25 @@ def hex_and_pad(i): @pytest.mark.parametrize( - 'event_abi,arguments,expected', + 'arguments,expected', ( ( - EVENT_1_ABI, {}, [[]], ), ( - EVENT_1_ABI, {'arg1': 1}, [[]], ), ( - EVENT_1_ABI, {'arg0': 1}, [[hex_and_pad(1), None, None]], ), ( - EVENT_1_ABI, {'arg0': [1]}, [[hex_and_pad(1), None, None]], ), ( - EVENT_1_ABI, {'arg0': [1, 2]}, [ [hex_and_pad(1), None, None], @@ -72,7 +67,6 @@ def hex_and_pad(i): ], ), ( - EVENT_1_ABI, {'arg0': [1, 3], 'arg3': [2, 4]}, [ [hex_and_pad(1), hex_and_pad(2), None], @@ -83,36 +77,31 @@ def hex_and_pad(i): ), ) ) -def test_construct_event_data_set(web3, event_abi, arguments, expected): - actual = construct_event_data_set(event_abi, web3.codec, arguments) +def test_construct_event_data_set(web3, arguments, expected): + actual = construct_event_data_set(EVENT_1_ABI, web3.codec, arguments) assert actual == expected @pytest.mark.parametrize( - 'event_abi,arguments,expected', + 'arguments,expected', ( ( - EVENT_1_ABI, {}, [[]], ), ( - EVENT_1_ABI, {'arg1': 1}, [[]], ), ( - EVENT_1_ABI, {'arg0': 1}, [[hex_and_pad(1), None, None]], ), ( - EVENT_1_ABI, {'arg0': [1]}, [[hex_and_pad(1), None, None]], ), ( - EVENT_1_ABI, {'arg0': [1, 2]}, [ [hex_and_pad(1), None, None], @@ -120,7 +109,6 @@ def test_construct_event_data_set(web3, event_abi, arguments, expected): ], ), ( - EVENT_1_ABI, {'arg0': [1, 3], 'arg3': [2, 4]}, [ [hex_and_pad(1), hex_and_pad(2), None], @@ -131,39 +119,34 @@ def test_construct_event_data_set(web3, event_abi, arguments, expected): ), ) ) -def test_construct_event_data_set_strict(web3_strict_types, event_abi, arguments, expected): - actual = construct_event_data_set(event_abi, web3_strict_types.codec, arguments) +def test_construct_event_data_set_strict(w3_strict_abi, arguments, expected): + actual = construct_event_data_set(EVENT_1_ABI, w3_strict_abi.codec, arguments) assert actual == expected @pytest.mark.parametrize( - 'event_abi,arguments,expected_error', + 'arguments,expected_error', ( ( - EVENT_2_ABI, {'arg0': '131414'}, EncodingTypeError, ), ( - EVENT_2_ABI, {'arg0': b'131414'}, ValueOutOfBounds, ), ( - EVENT_2_ABI, {'arg0': b'13'}, ValueOutOfBounds, ), ( - EVENT_2_ABI, {'arg0': b'12'}, ValueOutOfBounds, ), ) ) -def test_construct_event_data_set_strict_with_errors(web3_strict_types, - event_abi, +def test_construct_event_data_set_strict_with_errors(w3_strict_abi, arguments, expected_error): with pytest.raises(expected_error): - construct_event_data_set(event_abi, web3_strict_types.codec, arguments) + construct_event_data_set(EVENT_2_ABI, w3_strict_abi.codec, arguments) diff --git a/tests/core/utilities/test_construct_event_topics.py b/tests/core/utilities/test_construct_event_topics.py index 5190904d26..0931ba2cda 100644 --- a/tests/core/utilities/test_construct_event_topics.py +++ b/tests/core/utilities/test_construct_event_topics.py @@ -41,46 +41,39 @@ def hex_and_pad(i): @pytest.mark.parametrize( - 'event_abi,arguments,expected', + 'arguments,expected', ( ( - EVENT_1_ABI, {}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg0': 1}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg0': 1, 'arg3': [1, 2]}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg1': 1}, [ EVENT_1_TOPIC, hex_and_pad(1) ], ), ( - EVENT_1_ABI, {'arg1': [1, 2]}, [ EVENT_1_TOPIC, [hex_and_pad(1), hex_and_pad(2)], ], ), ( - EVENT_1_ABI, {'arg1': [1], 'arg2': [2]}, [ EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), ], ), ( - EVENT_1_ABI, {'arg1': [1, 3], 'arg2': [2, 4]}, [ EVENT_1_TOPIC, @@ -90,52 +83,45 @@ def hex_and_pad(i): ), ) ) -def test_construct_event_topics(web3, event_abi, arguments, expected): - actual = construct_event_topic_set(event_abi, web3.codec, arguments) +def test_construct_event_topics(web3, arguments, expected): + actual = construct_event_topic_set(EVENT_1_ABI, web3.codec, arguments) assert actual == expected @pytest.mark.parametrize( - 'event_abi,arguments,expected', + 'arguments,expected', ( ( - EVENT_1_ABI, {}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg0': 1}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg0': 1, 'arg3': [1, 2]}, [EVENT_1_TOPIC], ), ( - EVENT_1_ABI, {'arg1': 1}, [ EVENT_1_TOPIC, hex_and_pad(1) ], ), ( - EVENT_1_ABI, {'arg1': [1, 2]}, [ EVENT_1_TOPIC, [hex_and_pad(1), hex_and_pad(2)], ], ), ( - EVENT_1_ABI, {'arg1': [1], 'arg2': [2]}, [ EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), ], ), ( - EVENT_1_ABI, {'arg1': [1, 3], 'arg2': [2, 4]}, [ EVENT_1_TOPIC, @@ -145,44 +131,34 @@ def test_construct_event_topics(web3, event_abi, arguments, expected): ), ) ) -def test_construct_event_topics_strict(web3_strict_types, event_abi, arguments, expected): - actual = construct_event_topic_set(event_abi, web3_strict_types.codec, arguments) +def test_construct_event_topics_strict(w3_strict_abi, arguments, expected): + actual = construct_event_topic_set(EVENT_1_ABI, w3_strict_abi.codec, arguments) assert actual == expected @pytest.mark.parametrize( - 'event_abi,arguments,expected,error', + 'arguments,error', ( ( - EVENT_2_ABI, {'arg0': [b'123412']}, - [EVENT_2_TOPIC], ValueOutOfBounds, ), ( - EVENT_2_ABI, {'arg1': [b'']}, - [EVENT_2_TOPIC], ValueOutOfBounds, ), ( - EVENT_2_ABI, {'arg0': [b''], 'arg1': [b'']}, - [EVENT_2_TOPIC], ValueOutOfBounds, ), ( - EVENT_2_ABI, {'arg0': ['']}, - [EVENT_2_TOPIC], EncodingTypeError, ), ) ) -def test_construct_event_topics_strict_errors(web3_strict_types, - event_abi, +def test_construct_event_topics_strict_errors(w3_strict_abi, arguments, - expected, error): with pytest.raises(error): - construct_event_topic_set(event_abi, web3_strict_types.codec, arguments) + construct_event_topic_set(EVENT_2_ABI, w3_strict_abi.codec, arguments) diff --git a/tests/core/utilities/test_event_filter_builder.py b/tests/core/utilities/test_event_filter_builder.py index 6222e8f2cd..08ac40339e 100644 --- a/tests/core/utilities/test_event_filter_builder.py +++ b/tests/core/utilities/test_event_filter_builder.py @@ -1,5 +1,8 @@ import pytest +from eth_abi.exceptions import ( + ValueOutOfBounds, +) from hypothesis import ( given, strategies as st, @@ -50,3 +53,31 @@ def test_match_any_string_type_properties(web3, values): topic_filter = TopicArgumentFilter(arg_type="string", abi_codec=web3.codec) topic_filter.match_any(*values) assert len(topic_filter.match_values) == len(values) + + +@given(st.lists(elements=st.binary(), max_size=10, min_size=0)) +def test_match_any_bytes_type_properties(web3, values): + topic_filter = TopicArgumentFilter(arg_type="bytes", abi_codec=web3.codec) + topic_filter.match_any(*values) + assert len(topic_filter.match_values) == len(values) + + +@given(st.lists(elements=st.binary(), max_size=10, min_size=1)) +def test_match_any_bytes_type_properties_strict(w3_strict_abi, values): + topic_filter = TopicArgumentFilter(arg_type="bytes", abi_codec=w3_strict_abi.codec) + topic_filter.match_any(*values) + assert len(topic_filter.match_values) == len(values) + + +def test_match_hex_type_properties_strict(w3_strict_abi): + topic_filter = TopicArgumentFilter(arg_type="bytes2", abi_codec=w3_strict_abi.codec) + topic_filter.match_any("0x1233") + assert len(topic_filter.match_values) == 1 + + +@pytest.mark.parametrize("values", (b"123", b"1", "0x12", "0x", "0x121212")) +def test_match_any_bytes_type_properties_strict_errors(w3_strict_abi, values): + topic_filter = TopicArgumentFilter(arg_type="bytes2", abi_codec=w3_strict_abi.codec) + topic_filter.match_any(values) + with pytest.raises(ValueOutOfBounds): + topic_filter.match_values diff --git a/tests/core/utilities/test_validation.py b/tests/core/utilities/test_validation.py index ab8850a464..0dfe5359ee 100644 --- a/tests/core/utilities/test_validation.py +++ b/tests/core/utilities/test_validation.py @@ -135,8 +135,6 @@ def test_validation(param, validation, expected): ('bytes', "0x5402", None), ('bytes', "5402", TypeError), ('bytes', b'T\x02', None), - ('bytes2', b'T\x02', None), - ('bytes3', b'T\x02', None), ) ) def test_validate_abi_value(abi_type, value, expected): diff --git a/web3/_utils/abi.py b/web3/_utils/abi.py index b66586e590..92a181ee51 100644 --- a/web3/_utils/abi.py +++ b/web3/_utils/abi.py @@ -152,16 +152,37 @@ def validate_value(cls, value): super().validate_value(value) -class AcceptsHexStrMixin: - def validate(self): - super().validate() +class AcceptsHexStrEncoder(encoding.BaseEncoder): + def __init__(self, subencoder): + self.subencoder = subencoder + + @property + def is_dynamic(self): + return self.subencoder.is_dynamic + + @classmethod + def from_type_str(cls, abi_type, registry): + subencoder_cls = cls.get_subencoder_class() + subencoder = subencoder_cls.from_type_str(abi_type, registry) + return cls(subencoder) - if self.is_strict is None: - raise ValueError("`is_strict` may not be none") + @classmethod + def get_subencoder_class(cls): + if cls.subencoder_cls is None: + raise AttributeError(f'No subencoder class is set. {cls.__name__}') + return cls.subencoder_cls @combomethod def validate_value(self, value): - original_value = value + normalized_value = self.validate_and_normalize(value) + return self.subencoder.validate_value(normalized_value) + + def encode(self, value): + normalized_value = self.validate_and_normalize(value) + return self.subencoder.encode(normalized_value) + + def validate_and_normalize(self, value): + raw_value = value if is_text(value): try: value = decode_hex(value) @@ -171,33 +192,32 @@ def validate_value(self, value): msg=f'{value} is an invalid hex string', ) else: - self.check_for_0x(original_value) - - super().validate_value(value) - - def check_for_0x(self, original_value): - if original_value[:2] != '0x': - if self.is_strict: - self.invalidate_value( - original_value, - msg='hex string must be prefixed with 0x' - ) - elif original_value[:2] != '0x': - warnings.warn( - 'in v6 it will be invalid to pass a hex string without the "0x" prefix', - category=DeprecationWarning - ) + if raw_value[:2] != '0x': + if self.is_strict: + self.invalidate_value( + raw_value, + msg='hex string must be prefixed with 0x' + ) + elif raw_value[:2] != '0x': + warnings.warn( + 'in v6 it will be invalid to pass a hex string without the "0x" prefix', + category=DeprecationWarning + ) + return value -class BytesEncoder(AcceptsHexStrMixin, encoding.BytesEncoder): +class BytesEncoder(AcceptsHexStrEncoder): + subencoder_cls = encoding.BytesEncoder is_strict = False -class ByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder): +class ByteStringEncoder(AcceptsHexStrEncoder): + subencoder_cls = encoding.ByteStringEncoder is_strict = False -class StrictByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder): +class StrictByteStringEncoder(AcceptsHexStrEncoder): + subencoder_cls = encoding.ByteStringEncoder is_strict = True @@ -231,26 +251,26 @@ def validate(self): raise ValueError("Value byte size exceeds data size") def encode(self, value): - self.validate_value(value) - return self.encode_fn(value) + normalized_value = self.validate_value(value) + return self.encode_fn(normalized_value) def validate_value(self, value): if not is_bytes(value) and not is_text(value): self.invalidate_value(value) - original_value = value + raw_value = value if is_text(value): try: value = decode_hex(value) except binascii.Error: self.invalidate_value( value, - msg=f'{value} is an invalid hex string', + msg=f'{value} is not a valid hex string', ) else: - if original_value[:2] != '0x': + if raw_value[:2] != '0x': self.invalidate_value( - original_value, + raw_value, msg='hex string must be prefixed with 0x' ) @@ -267,6 +287,7 @@ def validate_value(self, value): exc=ValueOutOfBounds, msg="less than total byte size for bytes{} encoding".format(byte_size), ) + return value @staticmethod def encode_fn(value): @@ -280,6 +301,21 @@ def from_type_str(cls, abi_type, registry): ) +class BytesDecoder(decoding.FixedByteSizeDecoder): + is_big_endian = False + + @staticmethod + def decoder_fn(data): + return data + + @parse_type_str('bytes') + def from_type_str(cls, abi_type, registry): + return cls( + value_bit_size=abi_type.sub * 8, + data_byte_size=abi_type.sub, + ) + + class TextStringEncoder(encoding.TextStringEncoder): @classmethod def validate_value(cls, value): @@ -840,7 +876,7 @@ def build_strict_registry(): ) registry.register( BaseEquals('bytes', with_sub=True), - ExactLengthBytesEncoder, decoding.BytesDecoder, + ExactLengthBytesEncoder, BytesDecoder, label='bytes', ) registry.register( diff --git a/web3/_utils/filters.py b/web3/_utils/filters.py index ae44202c43..0814a47d11 100644 --- a/web3/_utils/filters.py +++ b/web3/_utils/filters.py @@ -209,6 +209,7 @@ def match_fn(w3, match_values_and_abi, data): compared to the abi decoded log data. """ abi_types, all_match_values = zip(*match_values_and_abi) + decoded_values = w3.codec.decode_abi(abi_types, HexBytes(data)) for data_value, match_values, abi_type in zip(decoded_values, all_match_values, abi_types): if match_values is None: