back to Claude Sonnet 3.5 - Fill-in summary
Claude Sonnet 3.5 - Fill-in: web3.py
Failed to run pytests for test tests
Traceback (most recent call last):
File "/testbed/.venv/bin/pytest", line 8, in <module>
sys.exit(console_main())
^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 201, in console_main
code = main()
^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 156, in main
config = _prepareconfig(args, plugins)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 341, in _prepareconfig
config = pluginmanager.hook.pytest_cmdline_parse(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
raise exception.with_traceback(exception.__traceback__)
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
teardown.throw(exception) # type: ignore[union-attr]
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/helpconfig.py", line 105, in pytest_cmdline_parse
config = yield
^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
res = hook_impl.function(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 1140, in pytest_cmdline_parse
self.parse(args)
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 1494, in parse
self._preparse(args, addopts=addopts)
File "/testbed/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 1381, in _preparse
self.pluginmanager.load_setuptools_entrypoints("pytest11")
File "/testbed/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 421, in load_setuptools_entrypoints
plugin = ep.load()
^^^^^^^^^
File "/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/importlib/metadata/__init__.py", line 205, in load
module = import_module(match.group('module'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/importlib/__init__.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 995, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "/testbed/web3/__init__.py", line 4, in <module>
from web3.providers import (
File "/testbed/web3/providers/__init__.py", line 1, in <module>
from .async_base import (
File "/testbed/web3/providers/async_base.py", line 4, in <module>
from web3._utils.encoding import FriendlyJsonSerde, Web3JsonEncoder
File "/testbed/web3/_utils/encoding.py", line 9, in <module>
from web3._utils.abi import is_address_type, is_array_type, is_bool_type, is_bytes_type, is_int_type, is_string_type, is_uint_type, size_of_type, sub_type_of_array_type
File "/testbed/web3/_utils/abi.py", line 17, in <module>
from web3._utils.ens import is_ens_name
ImportError: cannot import name 'is_ens_name' from 'web3._utils.ens' (/testbed/web3/_utils/ens.py)
Patch diff
diff --git a/web3/_utils/abi.py b/web3/_utils/abi.py
index 4fca64f0..47eb8332 100644
--- a/web3/_utils/abi.py
+++ b/web3/_utils/abi.py
@@ -30,7 +30,7 @@ def get_normalized_abi_arg_type(abi_arg: ABIEventParams) ->str:
makes use of `collapse_if_tuple()` to collapse the appropriate component
types within a tuple type, if present.
"""
- pass
+ return collapse_if_tuple(dict(abi_arg)["type"])
class AddressEncoder(encoding.AddressEncoder):
@@ -84,7 +84,34 @@ def merge_args_and_kwargs(function_abi: ABIFunction, args: Sequence[Any],
given. Returns a list of argument values aligned to the order of inputs
defined in ``function_abi``.
"""
- pass
+ if len(args) + len(kwargs) > len(function_abi.get('inputs', [])):
+ raise TypeError(
+ "Too many arguments: got {0} args and {1} kwargs, expected {2}".format(
+ len(args), len(kwargs), len(function_abi.get('inputs', []))
+ )
+ )
+
+ args_as_kwargs = {}
+ for arg_name, arg_value in zip(get_abi_input_names(function_abi), args):
+ args_as_kwargs[arg_name] = arg_value
+
+ for key, value in kwargs.items():
+ if key in args_as_kwargs:
+ raise TypeError(
+ "Duplicate argument name found: {0}".format(key)
+ )
+ args_as_kwargs[key] = value
+
+ unknown_kwargs = set(args_as_kwargs.keys()) - set(get_abi_input_names(function_abi))
+ if unknown_kwargs:
+ raise TypeError(
+ "Unknown arguments found: {0}".format(", ".join(unknown_kwargs))
+ )
+
+ return tuple(
+ args_as_kwargs.get(arg_abi['name'])
+ for arg_abi in function_abi.get('inputs', [])
+ )
TUPLE_TYPE_STR_RE = re.compile('^(tuple)((\\[([1-9]\\d*\\b)?])*)??$')
@@ -95,7 +122,12 @@ def get_tuple_type_str_parts(s: str) ->Optional[Tuple[str, Optional[str]]]:
Takes a JSON ABI type string. For tuple type strings, returns the separated
prefix and array dimension parts. For all other strings, returns ``None``.
"""
- pass
+ match = TUPLE_TYPE_STR_RE.match(s)
+ if match:
+ tuple_prefix = match.group(1)
+ tuple_dims = match.group(2) or None
+ return tuple_prefix, tuple_dims
+ return None
def _align_abi_input(arg_abi: ABIFunctionParams, arg: Any) ->Tuple[Any, ...]:
@@ -103,7 +135,19 @@ def _align_abi_input(arg_abi: ABIFunctionParams, arg: Any) ->Tuple[Any, ...]:
Aligns the values of any mapping at any level of nesting in ``arg``
according to the layout of the corresponding abi spec.
"""
- pass
+ if isinstance(arg, abc.Mapping):
+ return tuple(
+ _align_abi_input(component_abi, arg.get(component_abi['name'], None))
+ for component_abi in arg_abi['components']
+ )
+ elif isinstance(arg, abc.Iterable) and not isinstance(arg, str):
+ return tuple(
+ _align_abi_input(component_abi, component)
+ for component_abi, component
+ in zip(arg_abi['components'], arg)
+ )
+ else:
+ return arg,
def get_aligned_abi_inputs(abi: ABIFunction, args: Union[Tuple[Any, ...],
@@ -115,7 +159,24 @@ def get_aligned_abi_inputs(abi: ABIFunction, args: Union[Tuple[Any, ...],
contained in ``args`` may contain nested mappings or sequences corresponding
to tuple-encoded values in ``abi``.
"""
- pass
+ if isinstance(args, abc.Mapping):
+ kwargs = args
+ elif isinstance(args, abc.Sequence):
+ kwargs = merge_args_and_kwargs(abi, args, {})
+ else:
+ raise TypeError("Expected mapping or sequence, got {}".format(type(args)))
+
+ aligned_inputs = tuple(
+ _align_abi_input(arg_abi, kwargs.get(arg_abi['name']))
+ for arg_abi in abi.get('inputs', [])
+ )
+
+ input_types = tuple(
+ arg_abi['type']
+ for arg_abi in abi.get('inputs', [])
+ )
+
+ return input_types, aligned_inputs
DYNAMIC_TYPES = ['bytes', 'string']
@@ -137,7 +198,21 @@ def size_of_type(abi_type: TypeStr) ->int:
"""
Returns size in bits of abi_type
"""
- pass
+ if 'string' in abi_type:
+ return None
+ if 'byte' in abi_type:
+ return None
+ if '[' in abi_type:
+ return None
+ if abi_type == 'bool':
+ return 8
+ if abi_type == 'address':
+ return 160
+ if abi_type.startswith('uint'):
+ return int(abi_type[4:])
+ if abi_type.startswith('int'):
+ return int(abi_type[3:])
+ raise ValueError("Unknown type: {}".format(abi_type))
END_BRACKETS_OF_ARRAY_TYPE_REGEX = '\\[[^]]*\\]$'
diff --git a/web3/_utils/async_transactions.py b/web3/_utils/async_transactions.py
index 2285b1d0..47481deb 100644
--- a/web3/_utils/async_transactions.py
+++ b/web3/_utils/async_transactions.py
@@ -20,4 +20,33 @@ async def async_fill_transaction_defaults(async_w3: 'AsyncWeb3',
"""
if async_w3 is None, fill as much as possible while offline
"""
- pass
+ filled_transaction = cast(TxParams, {})
+ for key, default_getter in TRANSACTION_DEFAULTS.items():
+ if key not in transaction:
+ if callable(default_getter):
+ if async_w3 is None:
+ continue
+ default_val = (
+ await default_getter(async_w3, transaction)
+ if key in DYNAMIC_FEE_TXN_PARAMS
+ else await default_getter(async_w3, filled_transaction)
+ )
+ else:
+ default_val = default_getter
+ filled_transaction[key] = default_val
+
+ filled_transaction = merge(filled_transaction, transaction)
+
+ if async_w3 is not None:
+ if 'from' not in filled_transaction:
+ filled_transaction['from'] = await async_w3.eth.default_account
+
+ if filled_transaction.get('nonce') is None:
+ filled_transaction['nonce'] = await async_w3.eth.get_transaction_count(
+ filled_transaction['from']
+ )
+
+ if 'chainId' not in filled_transaction:
+ filled_transaction['chainId'] = await async_w3.eth.chain_id
+
+ return filled_transaction
diff --git a/web3/_utils/caching.py b/web3/_utils/caching.py
index bd619a5d..e0d704ff 100644
--- a/web3/_utils/caching.py
+++ b/web3/_utils/caching.py
@@ -6,11 +6,26 @@ if TYPE_CHECKING:
from web3.types import RPCEndpoint
-def generate_cache_key(value: Any) ->str:
+def generate_cache_key(value: Any) -> str:
"""
Generates a cache key for the *args and **kwargs
"""
- pass
+ if is_null(value):
+ return "null"
+ elif is_boolean(value):
+ return "bool:%s" % str(value).lower()
+ elif is_number(value):
+ return "num:%d" % value
+ elif is_text(value):
+ return "text:%s" % value
+ elif is_bytes(value):
+ return "bytes:%s" % hashlib.md5(value).hexdigest()
+ elif is_list_like(value):
+ return "list:%s" % hashlib.md5(to_bytes(str(value))).hexdigest()
+ elif is_dict(value):
+ return "dict:%s" % hashlib.md5(to_bytes(str(sorted(value.items())))).hexdigest()
+ else:
+ return "obj:%s" % hashlib.md5(to_bytes(str(value))).hexdigest()
class RequestInformation:
diff --git a/web3/_utils/contracts.py b/web3/_utils/contracts.py
index 264553ef..ef2aa3ec 100644
--- a/web3/_utils/contracts.py
+++ b/web3/_utils/contracts.py
@@ -23,7 +23,23 @@ def extract_argument_types(*args: Sequence[Any]) ->str:
Takes a list of arguments and returns a string representation of the argument types,
appropriately collapsing `tuple` types into the respective nested types.
"""
- pass
+ def get_type(arg):
+ if isinstance(arg, tuple):
+ return f"({','.join(get_type(a) for a in arg)})"
+ elif isinstance(arg, list):
+ return f"{get_type(arg[0]) if arg else 'unknown'}[]"
+ elif isinstance(arg, int):
+ return "uint256" # Assuming uint256 for integers
+ elif isinstance(arg, bool):
+ return "bool"
+ elif isinstance(arg, str):
+ return "string"
+ elif isinstance(arg, bytes):
+ return "bytes"
+ else:
+ return "unknown"
+
+ return ",".join(get_type(arg) for arg in args)
def prepare_transaction(address: ChecksumAddress, w3: Union['AsyncWeb3',
@@ -37,11 +53,56 @@ def prepare_transaction(address: ChecksumAddress, w3: Union['AsyncWeb3',
TODO: make this a public API
TODO: add new prepare_deploy_transaction API
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_identifier is FallbackFn:
+ fn_abi = get_fallback_func_abi(contract_abi)
+ elif fn_identifier is ReceiveFn:
+ fn_abi = get_receive_func_abi(contract_abi)
+ elif not is_text(fn_identifier):
+ raise TypeError("Unsupported function identifier")
+ elif fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, fn_identifier, fn_args, fn_kwargs)
+
+ validate_payable(transaction, fn_abi)
+
+ if transaction.get('data'):
+ raise ValueError("Transaction parameter may not contain a 'data' key")
+
+ if transaction.get('to') and transaction['to'] != address:
+ raise ValueError("Supplied 'to' address in transaction does not match the contract address")
+
+ if 'to' not in transaction:
+ transaction['to'] = address
+
+ if 'value' not in transaction:
+ transaction['value'] = 0
+
+ if fn_identifier is FallbackFn:
+ transaction['data'] = '0x'
+ elif fn_identifier is ReceiveFn:
+ transaction['data'] = '0x'
+ else:
+ transaction['data'] = encode_transaction_data(
+ w3,
+ fn_identifier,
+ contract_abi,
+ fn_abi,
+ fn_args,
+ fn_kwargs,
+ )
+
+ return transaction
def validate_payable(transaction: TxParams, abi: ABIFunction) ->None:
"""Raise Web3ValidationError if non-zero ether
is sent to a non-payable function.
"""
- pass
+ if 'value' in transaction:
+ if transaction['value'] != 0:
+ if "payable" not in abi.get('stateMutability', '') and not abi.get('payable', False):
+ raise Web3ValidationError(
+ "Sending non-zero ether to a non-payable function"
+ )
diff --git a/web3/_utils/decorators.py b/web3/_utils/decorators.py
index 3a2d3a9a..ac21d200 100644
--- a/web3/_utils/decorators.py
+++ b/web3/_utils/decorators.py
@@ -9,7 +9,27 @@ def reject_recursive_repeats(to_wrap: Callable[..., Any]) ->Callable[..., Any]:
"""
Prevent simple cycles by returning None when called recursively with same instance
"""
- pass
+ thread_local = threading.local()
+
+ @functools.wraps(to_wrap)
+ def wrapped(*args: Any, **kwargs: Any) -> Any:
+ arg_instances = tuple(map(id, args))
+ kwarg_instances = tuple(map(id, kwargs.values()))
+ key = (to_wrap, arg_instances, kwarg_instances)
+
+ if hasattr(thread_local, 'reject_recursive_repeats_func_args'):
+ if key in thread_local.reject_recursive_repeats_func_args:
+ return None
+ else:
+ thread_local.reject_recursive_repeats_func_args = set()
+
+ try:
+ thread_local.reject_recursive_repeats_func_args.add(key)
+ return to_wrap(*args, **kwargs)
+ finally:
+ thread_local.reject_recursive_repeats_func_args.remove(key)
+
+ return wrapped
def deprecate_method(replacement_method: str=None, deprecation_msg: str=None
@@ -28,4 +48,27 @@ def deprecate_method(replacement_method: str=None, deprecation_msg: str=None
def some_method(arg):
...
"""
- pass
+ def decorator(func: TFunc) -> TFunc:
+ @functools.wraps(func)
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
+ if replacement_method:
+ warnings.warn(
+ f"{func.__name__} is deprecated. Use {replacement_method} instead.",
+ DeprecationWarning,
+ stacklevel=2
+ )
+ elif deprecation_msg:
+ warnings.warn(
+ f"{func.__name__} is deprecated. {deprecation_msg}",
+ DeprecationWarning,
+ stacklevel=2
+ )
+ else:
+ warnings.warn(
+ f"{func.__name__} is deprecated.",
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return func(*args, **kwargs)
+ return cast(TFunc, wrapper)
+ return decorator
diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py
index 0797d189..95c8cccd 100644
--- a/web3/_utils/encoding.py
+++ b/web3/_utils/encoding.py
@@ -16,28 +16,62 @@ def hex_encode_abi_type(abi_type: TypeStr, value: Any, force_size: Optional
"""
Encodes value into a hex string in format of abi_type
"""
- pass
+ validate_abi_type(abi_type)
+ validate_abi_value(abi_type, value)
+
+ if is_address_type(abi_type):
+ return to_hex(value)
+ elif is_bool_type(abi_type):
+ return to_hex_with_size(value, 8)
+ elif is_uint_type(abi_type):
+ return to_hex_with_size(value, size_of_type(abi_type))
+ elif is_int_type(abi_type):
+ return to_hex_twos_compliment(value, size_of_type(abi_type))
+ elif is_bytes_type(abi_type):
+ if force_size is not None:
+ return pad_hex(to_hex(value), force_size)
+ else:
+ size = size_of_type(abi_type)
+ return pad_hex(to_hex(value), size)
+ elif is_string_type(abi_type):
+ return to_hex(text_if_str(to_bytes, value))
+ elif is_array_type(abi_type):
+ sub_type = sub_type_of_array_type(abi_type)
+ return "".join([remove_0x_prefix(hex_encode_abi_type(sub_type, v)) for v in value])
+ else:
+ raise ValueError(f"Unsupported ABI type: {abi_type}")
def to_hex_twos_compliment(value: Any, bit_size: int) ->HexStr:
"""
Converts integer value to twos compliment hex representation with given bit_size
"""
- pass
+ if value >= 0:
+ return to_hex_with_size(value, bit_size)
+ else:
+ return to_hex(value & ((1 << bit_size) - 1))
def to_hex_with_size(value: Any, bit_size: int) ->HexStr:
"""
Converts a value to hex with given bit_size:
"""
- pass
+ if isinstance(value, str):
+ value = int(value, 16)
+ if isinstance(value, int):
+ hex_value = hex(value)[2:] # remove '0x' prefix
+ return pad_hex(hex_value, bit_size)
+ else:
+ raise ValueError(f"Cannot convert {value} to hex with size {bit_size}")
def pad_hex(value: Any, bit_size: int) ->HexStr:
"""
Pads a hex string up to the given bit_size
"""
- pass
+ value = remove_0x_prefix(value)
+ padded = value.zfill(bit_size // 4)
+ return add_0x_prefix(padded)
zpad_bytes = pad_bytes(b'\x00')
@@ -53,7 +87,10 @@ def text_if_str(to_type: Callable[..., str], text_or_primitive: Union[
text=text), eg~ to_bytes, to_text, to_hex, to_int, etc
@param text_or_primitive in bytes, str, or int.
"""
- pass
+ if isinstance(text_or_primitive, str):
+ return to_type(text=text_or_primitive)
+ else:
+ return to_type(text_or_primitive)
@curry
@@ -66,7 +103,10 @@ def hexstr_if_str(to_type: Callable[..., HexStr], hexstr_or_primitive:
text=text), eg~ to_bytes, to_text, to_hex, to_int, etc
@param hexstr_or_primitive in bytes, str, or int.
"""
- pass
+ if isinstance(hexstr_or_primitive, str):
+ return to_type(hexstr=hexstr_or_primitive)
+ else:
+ return to_type(hexstr_or_primitive)
class FriendlyJsonSerde:
@@ -91,4 +131,4 @@ def to_json(obj: Dict[Any, Any]) ->str:
"""
Convert a complex object (like a transaction object) to a JSON string
"""
- pass
+ return json.dumps(obj, cls=Web3JsonEncoder)
diff --git a/web3/_utils/ens.py b/web3/_utils/ens.py
index 1bddc8b2..c7c23b7d 100644
--- a/web3/_utils/ens.py
+++ b/web3/_utils/ens.py
@@ -25,4 +25,9 @@ def contract_ens_addresses(contract: 'Contract', name_addr_pairs: Dict[str,
with contract_ens_addresses(mycontract, [('resolve-as-1s.eth', '0x111...111')]):
# any contract call or transaction in here would only resolve the above ENS pair
"""
- pass
+ original_ens = contract.w3.ens
+ try:
+ contract.w3.ens = StaticENS(name_addr_pairs)
+ yield
+ finally:
+ contract.w3.ens = original_ens
diff --git a/web3/_utils/error_formatters_utils.py b/web3/_utils/error_formatters_utils.py
index f81e35c0..e3de343c 100644
--- a/web3/_utils/error_formatters_utils.py
+++ b/web3/_utils/error_formatters_utils.py
@@ -24,15 +24,28 @@ PANIC_ERROR_CODES = {'00':
MISSING_DATA = 'no data'
-def _parse_error_with_reverted_prefix(data: str) ->str:
+def _parse_error_with_reverted_prefix(data: str) -> str:
"""
Parse errors from the data string which begin with the "Reverted" prefix.
"Reverted", function selector and offset are always the same for revert errors
"""
- pass
+ if data.startswith('Reverted '):
+ data = data[9:] # Remove "Reverted " prefix
+ if data.startswith('0x'):
+ data = data[2:] # Remove "0x" prefix if present
+ if len(data) < 8 + 64 + 64: # Minimum length for a valid revert error
+ return MISSING_DATA
+ # Skip function selector (4 bytes) and offset (32 bytes)
+ error_data = data[8 + 64:]
+ # Decode the error message
+ try:
+ error_message = abi.decode(['string'], to_bytes(hexstr=error_data))[0]
+ return error_message
+ except:
+ return MISSING_DATA
-def _raise_contract_error(response_error_data: str) ->None:
+def _raise_contract_error(response_error_data: str) -> None:
"""
Decode response error from data string and raise appropriate exception.
@@ -42,10 +55,29 @@ def _raise_contract_error(response_error_data: str) ->None:
String length (32 bytes)
Reason string (padded, use string length from above to get meaningful part)
"""
- pass
+ if response_error_data.startswith('Reverted '):
+ response_error_data = response_error_data[9:]
+ if response_error_data.startswith('0x'):
+ response_error_data = response_error_data[2:]
+ if response_error_data.startswith(SOLIDITY_ERROR_FUNC_SELECTOR[2:]):
+ error_msg = _parse_error_with_reverted_prefix(response_error_data)
+ raise ContractLogicError(error_msg)
+ elif response_error_data.startswith(PANIC_ERROR_FUNC_SELECTOR[2:]):
+ panic_code = response_error_data[8:10]
+ panic_msg = PANIC_ERROR_CODES.get(panic_code, f"Unknown panic error code: {panic_code}")
+ raise ContractPanicError(panic_msg)
+ elif response_error_data.startswith(OFFCHAIN_LOOKUP_FUNC_SELECTOR[2:]):
+ try:
+ decoded = abi.decode(list(OFFCHAIN_LOOKUP_FIELDS.values()), to_bytes(hexstr=response_error_data[10:]))
+ raise OffchainLookup(dict(zip(OFFCHAIN_LOOKUP_FIELDS.keys(), decoded)))
+ except:
+ raise ContractLogicError("Failed to decode OffchainLookup error")
+ else:
+ raise ContractCustomError(f"Unknown error: {response_error_data}")
-def raise_contract_logic_error_on_revert(response: RPCResponse) ->RPCResponse:
+
+def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:
"""
Revert responses contain an error with the following optional attributes:
`code` - in this context, used for an unknown edge case when code = '3'
@@ -54,13 +86,40 @@ def raise_contract_logic_error_on_revert(response: RPCResponse) ->RPCResponse:
See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert
"""
- pass
+ if 'error' not in response:
+ return response
+
+ error = response['error']
+ message = error.get('message', '')
+ code = error.get('code')
+ data = error.get('data')
+
+ if code == 3:
+ raise ContractLogicError(message)
+
+ if isinstance(data, str):
+ if data.startswith('Reverted ') or data.startswith('0x'):
+ _raise_contract_error(data)
+ else:
+ raise ContractLogicError(data)
+ elif isinstance(data, dict) and isinstance(data.get('message'), str):
+ raise ContractLogicError(data['message'])
+ elif message:
+ raise ContractLogicError(message)
+ else:
+ raise ContractLogicError("Unspecified contract error")
+
+ return response # This line will never be reached, but it's kept for consistency
-def raise_transaction_indexing_error_if_indexing(response: RPCResponse
- ) ->RPCResponse:
+def raise_transaction_indexing_error_if_indexing(response: RPCResponse) -> RPCResponse:
"""
Raise an error if ``eth_getTransactionReceipt`` returns an error indicating that
transactions are still being indexed.
"""
- pass
+ if 'error' in response:
+ error = response['error']
+ message = error.get('message', '')
+ if 'still indexing' in message.lower():
+ raise TransactionIndexingInProgress(message)
+ return response
diff --git a/web3/_utils/events.py b/web3/_utils/events.py
index 98c9092d..d1ebf623 100644
--- a/web3/_utils/events.py
+++ b/web3/_utils/events.py
@@ -29,7 +29,11 @@ def get_event_abi_types_for_decoding(event_inputs: Sequence[ABIEventParams]
`string`. Because of this we need to modify the types so that we can
decode the log entries using the correct types.
"""
- pass
+ for input in event_inputs:
+ if input['indexed'] and input['type'] in ('string', 'bytes'):
+ yield 'bytes32'
+ else:
+ yield input['type']
@curry
@@ -39,7 +43,47 @@ def get_event_data(abi_codec: ABICodec, event_abi: ABIEvent, log_entry:
Given an event ABI and a log entry for that event, return the decoded
event data
"""
- pass
+ log_topics = log_entry['topics']
+ log_data = log_entry['data']
+
+ # Validate that the first topic is the event signature
+ if event_abi_to_log_topic(event_abi) != log_topics[0]:
+ raise MismatchedABI("Event signature mismatch")
+
+ indexed_inputs = get_indexed_event_inputs(event_abi)
+ non_indexed_inputs = exclude_indexed_event_inputs(event_abi)
+
+ # Decode indexed inputs
+ decoded_indexed_data = abi_codec.decode(
+ [input['type'] for input in indexed_inputs],
+ b''.join(log_topics[1:])
+ )
+
+ # Decode non-indexed inputs
+ decoded_non_indexed_data = abi_codec.decode(
+ [input['type'] for input in non_indexed_inputs],
+ log_data
+ )
+
+ # Combine decoded data
+ decoded_data = list(decoded_indexed_data) + list(decoded_non_indexed_data)
+
+ # Create a dictionary of decoded data
+ event_data = {
+ 'args': AttributeDict(dict(zip(
+ [input['name'] for input in event_abi['inputs']],
+ decoded_data
+ ))),
+ 'event': event_abi['name'],
+ 'logIndex': log_entry['logIndex'],
+ 'transactionIndex': log_entry['transactionIndex'],
+ 'transactionHash': log_entry['transactionHash'],
+ 'address': log_entry['address'],
+ 'blockHash': log_entry['blockHash'],
+ 'blockNumber': log_entry['blockNumber'],
+ }
+
+ return AttributeDict(event_data)
normalize_topic_list = compose(remove_trailing_from_seq(remove_value=None),
diff --git a/web3/_utils/filters.py b/web3/_utils/filters.py
index ef9ab1d2..abe78a49 100644
--- a/web3/_utils/filters.py
+++ b/web3/_utils/filters.py
@@ -34,13 +34,13 @@ class BaseFilter:
Hook for subclasses to change the format of the value that is passed
into the callback functions.
"""
- pass
+ return entry
def is_valid_entry(self, entry: LogReceipt) ->bool:
"""
Hook for subclasses to implement additional filtering layers.
"""
- pass
+ return True
class Filter(BaseFilter):
@@ -95,7 +95,15 @@ class LogFilter(Filter):
Expects a set of tuples with the type and value, e.g.:
(('uint256', [12345, 54321]), ('string', ('a-single-string',)))
"""
- pass
+ self.data_filter_set = data_filter_set
+ self.data_filter_set_regex = []
+ self.data_filter_set_function = []
+
+ for data_type, data_value in data_filter_set:
+ if is_string(data_value) and not is_hex(data_value):
+ self.data_filter_set_regex.append((data_type, data_value))
+ else:
+ self.data_filter_set_function.append((data_type, data_value))
class AsyncLogFilter(AsyncFilter):
@@ -120,7 +128,15 @@ class AsyncLogFilter(AsyncFilter):
Expects a set of tuples with the type and value, e.g.:
(('uint256', [12345, 54321]), ('string', ('a-single-string',)))
"""
- pass
+ self.data_filter_set = data_filter_set
+ self.data_filter_set_regex = []
+ self.data_filter_set_function = []
+
+ for data_type, data_value in data_filter_set:
+ if is_string(data_value) and not is_hex(data_value):
+ self.data_filter_set_regex.append((data_type, data_value))
+ else:
+ self.data_filter_set_function.append((data_type, data_value))
not_text = complement(is_text)
@@ -133,7 +149,10 @@ def normalize_data_values(type_string: TypeStr, data_value: Any) ->Any:
eth-abi v1 returns utf-8 bytes for string values.
This can be removed once eth-abi v2 is required.
"""
- pass
+ if type_string == 'string':
+ return normalize_to_text(data_value)
+ else:
+ return data_value
@curry
@@ -144,7 +163,18 @@ def match_fn(codec: ABICodec, match_values_and_abi: Collection[Tuple[str,
Values provided through the match_values_and_abi parameter are
compared to the abi decoded log data.
"""
- pass
+ types, match_values = zip(*match_values_and_abi)
+ decoded_values = codec.decode(types, HexBytes(data))
+ normalized_decoded_values = [
+ normalize_data_values(type_string, data_value)
+ for type_string, data_value
+ in zip(types, decoded_values)
+ ]
+ return all(
+ match_value == decoded_value
+ for match_value, decoded_value
+ in zip(match_values, normalized_decoded_values)
+ )
class _UseExistingFilter(Exception):
diff --git a/web3/_utils/formatters.py b/web3/_utils/formatters.py
index 542d1594..2ef72506 100644
--- a/web3/_utils/formatters.py
+++ b/web3/_utils/formatters.py
@@ -15,7 +15,11 @@ def map_collection(func: Callable[..., TReturn], collection: Any) ->Any:
Apply func to each element of a collection, or value of a dictionary.
If the value is not a collection, return it unmodified
"""
- pass
+ if isinstance(collection, Mapping):
+ return {key: func(val) for key, val in collection.items()}
+ elif is_list_like(collection):
+ return [func(val) for val in collection]
+ return collection
@reject_recursive_repeats
@@ -25,4 +29,4 @@ def recursive_map(func: Callable[..., TReturn], data: Any) ->TReturn:
Define func so that it only applies to the type of value that you
want it to apply to.
"""
- pass
+ return map_collection(lambda item: recursive_map(func, item), func(data))
diff --git a/web3/_utils/function_identifiers.py b/web3/_utils/function_identifiers.py
index b3f650b7..064855ae 100644
--- a/web3/_utils/function_identifiers.py
+++ b/web3/_utils/function_identifiers.py
@@ -1,6 +1,34 @@
class FallbackFn:
- pass
+ """
+ Represents a fallback function in Solidity contracts.
+ """
+ def __init__(self):
+ self.selector = b''
+ self.abi = {
+ 'type': 'fallback',
+ 'stateMutability': 'payable'
+ }
+
+ def __str__(self):
+ return '<fallback>'
+
+ def __repr__(self):
+ return f'FallbackFn()'
class ReceiveFn:
- pass
+ """
+ Represents a receive function in Solidity contracts.
+ """
+ def __init__(self):
+ self.selector = b''
+ self.abi = {
+ 'type': 'receive',
+ 'stateMutability': 'payable'
+ }
+
+ def __str__(self):
+ return '<receive>'
+
+ def __repr__(self):
+ return f'ReceiveFn()'
diff --git a/web3/_utils/math.py b/web3/_utils/math.py
index e2ea76f7..e51fe133 100644
--- a/web3/_utils/math.py
+++ b/web3/_utils/math.py
@@ -5,4 +5,24 @@ from web3.exceptions import InsufficientData
def percentile(values: Optional[Sequence[int]]=None, percentile: Optional[
float]=None) ->float:
"""Calculates a simplified weighted average percentile"""
- pass
+ if values is None or percentile is None:
+ raise InsufficientData("Both 'values' and 'percentile' must be provided")
+
+ if not 0 <= percentile <= 100:
+ raise ValueError("Percentile must be between 0 and 100")
+
+ if not values:
+ raise InsufficientData("The 'values' sequence cannot be empty")
+
+ sorted_values = sorted(values)
+ index = (len(sorted_values) - 1) * percentile / 100
+
+ if index.is_integer():
+ return float(sorted_values[int(index)])
+ else:
+ lower_index = int(index)
+ upper_index = lower_index + 1
+ lower_value = sorted_values[lower_index]
+ upper_value = sorted_values[upper_index]
+ fraction = index - lower_index
+ return lower_value + (upper_value - lower_value) * fraction
diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py
index 96fd37e1..aae675c4 100644
--- a/web3/_utils/method_formatters.py
+++ b/web3/_utils/method_formatters.py
@@ -37,7 +37,10 @@ def type_aware_apply_formatters_to_dict(formatters: Formatters, value:
"""
Preserve ``AttributeDict`` types if original ``value`` was an ``AttributeDict``.
"""
- pass
+ formatted_dict = apply_formatters_to_dict(formatters, value)
+ if isinstance(value, AttributeDict):
+ return AttributeDict.recursive(formatted_dict)
+ return formatted_dict
def type_aware_apply_formatters_to_dict_keys_and_values(key_formatters:
@@ -47,7 +50,13 @@ def type_aware_apply_formatters_to_dict_keys_and_values(key_formatters:
"""
Preserve ``AttributeDict`` types if original ``value`` was an ``AttributeDict``.
"""
- pass
+ formatted_dict = {
+ key_formatters(key): value_formatters(value)
+ for key, value in dict_like_object.items()
+ }
+ if isinstance(dict_like_object, AttributeDict):
+ return AttributeDict.recursive(formatted_dict)
+ return formatted_dict
ACCESS_LIST_FORMATTER = type_aware_apply_formatters_to_dict({'address':
diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py
index 2f158deb..def52f16 100644
--- a/web3/_utils/module_testing/eth_module.py
+++ b/web3/_utils/module_testing/eth_module.py
@@ -50,8 +50,266 @@ if TYPE_CHECKING:
class AsyncEthModuleTest:
- pass
+ async def test_eth_gas_price(self, async_w3: "AsyncWeb3") -> None:
+ gas_price = await async_w3.eth.gas_price
+ assert is_integer(gas_price)
+ assert gas_price > 0
+
+ async def test_eth_max_priority_fee(self, async_w3: "AsyncWeb3") -> None:
+ max_priority_fee = await async_w3.eth.max_priority_fee
+ assert is_integer(max_priority_fee)
+ assert max_priority_fee > 0
+
+ async def test_eth_accounts(self, async_w3: "AsyncWeb3") -> None:
+ accounts = await async_w3.eth.accounts
+ assert is_list_like(accounts)
+ assert len(accounts) != 0
+ assert all(is_checksum_address(account) for account in accounts)
+
+ async def test_eth_block_number(self, async_w3: "AsyncWeb3") -> None:
+ block_number = await async_w3.eth.block_number
+ assert is_integer(block_number)
+ assert block_number >= 0
+
+ async def test_eth_get_block_number(self, async_w3: "AsyncWeb3") -> None:
+ block_number = await async_w3.eth.get_block_number()
+ assert is_integer(block_number)
+ assert block_number >= 0
+
+ async def test_eth_get_balance(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ balance = await async_w3.eth.get_balance(coinbase)
+ assert is_integer(balance)
+ assert balance >= 0
+
+ async def test_eth_get_storage_at(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ storage = await async_w3.eth.get_storage_at(coinbase, 0)
+ assert isinstance(storage, HexBytes)
+
+ async def test_eth_get_transaction_count(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ transaction_count = await async_w3.eth.get_transaction_count(coinbase)
+ assert is_integer(transaction_count)
+ assert transaction_count >= 0
+
+ async def test_eth_get_block(self, async_w3: "AsyncWeb3") -> None:
+ latest_block = await async_w3.eth.get_block('latest')
+ assert isinstance(latest_block, BlockData)
+ assert latest_block['number'] > 0
+
+ async def test_eth_get_code(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ code = await async_w3.eth.get_code(coinbase)
+ assert isinstance(code, HexBytes)
+
+ async def test_eth_sign(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ signature = await async_w3.eth.sign(coinbase, text='Hello World')
+ assert isinstance(signature, HexBytes)
+ assert len(signature) == 65
+
+ async def test_eth_send_transaction(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = await async_w3.eth.send_transaction(transaction)
+ assert isinstance(tx_hash, HexBytes)
+
+ async def test_eth_get_transaction(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = await async_w3.eth.send_transaction(transaction)
+ tx = await async_w3.eth.get_transaction(tx_hash)
+ assert isinstance(tx, TxData)
+ assert tx['hash'] == tx_hash
+
+ async def test_eth_get_transaction_receipt(self, async_w3: "AsyncWeb3") -> None:
+ coinbase = await async_w3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = await async_w3.eth.send_transaction(transaction)
+ receipt = await async_w3.eth.wait_for_transaction_receipt(tx_hash)
+ assert isinstance(receipt, TxData)
+ assert receipt['transactionHash'] == tx_hash
+
+ async def test_eth_get_transaction_receipt_unmined(self, async_w3: "AsyncWeb3") -> None:
+ with pytest.raises(TransactionNotFound):
+ await async_w3.eth.get_transaction_receipt(UNKNOWN_HASH)
+
+ async def test_eth_get_transaction_by_block(self, async_w3: "AsyncWeb3") -> None:
+ block = await async_w3.eth.get_block('latest')
+ if len(block['transactions']) > 0:
+ transaction = await async_w3.eth.get_transaction_by_block(block['number'], 0)
+ assert isinstance(transaction, TxData)
+
+ async def test_eth_get_uncle_by_block(self, async_w3: "AsyncWeb3") -> None:
+ block = await async_w3.eth.get_block('latest')
+ if len(block['uncles']) > 0:
+ uncle = await async_w3.eth.get_uncle_by_block(block['number'], 0)
+ assert isinstance(uncle, BlockData)
+
+ async def test_eth_get_compilers(self, async_w3: "AsyncWeb3") -> None:
+ compilers = await async_w3.eth.get_compilers()
+ assert is_list_like(compilers)
+
+ async def test_eth_syncing(self, async_w3: "AsyncWeb3") -> None:
+ syncing = await async_w3.eth.syncing
+ assert is_boolean(syncing) or isinstance(syncing, SyncStatus)
+
+ async def test_eth_mining(self, async_w3: "AsyncWeb3") -> None:
+ mining = await async_w3.eth.mining
+ assert is_boolean(mining)
+
+ async def test_eth_hashrate(self, async_w3: "AsyncWeb3") -> None:
+ hashrate = await async_w3.eth.hashrate
+ assert is_integer(hashrate)
+ assert hashrate >= 0
+
+ async def test_eth_chain_id(self, async_w3: "AsyncWeb3") -> None:
+ chain_id = await async_w3.eth.chain_id
+ assert is_integer(chain_id)
+ assert chain_id > 0
class EthModuleTest:
- pass
+ def test_eth_gas_price(self, web3: "Web3") -> None:
+ gas_price = web3.eth.gas_price
+ assert is_integer(gas_price)
+ assert gas_price > 0
+
+ def test_eth_max_priority_fee(self, web3: "Web3") -> None:
+ max_priority_fee = web3.eth.max_priority_fee
+ assert is_integer(max_priority_fee)
+ assert max_priority_fee > 0
+
+ def test_eth_accounts(self, web3: "Web3") -> None:
+ accounts = web3.eth.accounts
+ assert is_list_like(accounts)
+ assert len(accounts) != 0
+ assert all(is_checksum_address(account) for account in accounts)
+
+ def test_eth_block_number(self, web3: "Web3") -> None:
+ block_number = web3.eth.block_number
+ assert is_integer(block_number)
+ assert block_number >= 0
+
+ def test_eth_get_block_number(self, web3: "Web3") -> None:
+ block_number = web3.eth.get_block_number()
+ assert is_integer(block_number)
+ assert block_number >= 0
+
+ def test_eth_get_balance(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ balance = web3.eth.get_balance(coinbase)
+ assert is_integer(balance)
+ assert balance >= 0
+
+ def test_eth_get_storage_at(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ storage = web3.eth.get_storage_at(coinbase, 0)
+ assert isinstance(storage, HexBytes)
+
+ def test_eth_get_transaction_count(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ transaction_count = web3.eth.get_transaction_count(coinbase)
+ assert is_integer(transaction_count)
+ assert transaction_count >= 0
+
+ def test_eth_get_block(self, web3: "Web3") -> None:
+ latest_block = web3.eth.get_block('latest')
+ assert isinstance(latest_block, BlockData)
+ assert latest_block['number'] > 0
+
+ def test_eth_get_code(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ code = web3.eth.get_code(coinbase)
+ assert isinstance(code, HexBytes)
+
+ def test_eth_sign(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ signature = web3.eth.sign(coinbase, text='Hello World')
+ assert isinstance(signature, HexBytes)
+ assert len(signature) == 65
+
+ def test_eth_send_transaction(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = web3.eth.send_transaction(transaction)
+ assert isinstance(tx_hash, HexBytes)
+
+ def test_eth_get_transaction(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = web3.eth.send_transaction(transaction)
+ tx = web3.eth.get_transaction(tx_hash)
+ assert isinstance(tx, TxData)
+ assert tx['hash'] == tx_hash
+
+ def test_eth_get_transaction_receipt(self, web3: "Web3") -> None:
+ coinbase = web3.eth.coinbase
+ transaction = {
+ 'to': UNKNOWN_ADDRESS,
+ 'from': coinbase,
+ 'value': 1,
+ }
+ tx_hash = web3.eth.send_transaction(transaction)
+ receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
+ assert isinstance(receipt, TxData)
+ assert receipt['transactionHash'] == tx_hash
+
+ def test_eth_get_transaction_receipt_unmined(self, web3: "Web3") -> None:
+ with pytest.raises(TransactionNotFound):
+ web3.eth.get_transaction_receipt(UNKNOWN_HASH)
+
+ def test_eth_get_transaction_by_block(self, web3: "Web3") -> None:
+ block = web3.eth.get_block('latest')
+ if len(block['transactions']) > 0:
+ transaction = web3.eth.get_transaction_by_block(block['number'], 0)
+ assert isinstance(transaction, TxData)
+
+ def test_eth_get_uncle_by_block(self, web3: "Web3") -> None:
+ block = web3.eth.get_block('latest')
+ if len(block['uncles']) > 0:
+ uncle = web3.eth.get_uncle_by_block(block['number'], 0)
+ assert isinstance(uncle, BlockData)
+
+ def test_eth_get_compilers(self, web3: "Web3") -> None:
+ compilers = web3.eth.get_compilers()
+ assert is_list_like(compilers)
+
+ def test_eth_syncing(self, web3: "Web3") -> None:
+ syncing = web3.eth.syncing
+ assert is_boolean(syncing) or isinstance(syncing, SyncStatus)
+
+ def test_eth_mining(self, web3: "Web3") -> None:
+ mining = web3.eth.mining
+ assert is_boolean(mining)
+
+ def test_eth_hashrate(self, web3: "Web3") -> None:
+ hashrate = web3.eth.hashrate
+ assert is_integer(hashrate)
+ assert hashrate >= 0
+
+ def test_eth_chain_id(self, web3: "Web3") -> None:
+ chain_id = web3.eth.chain_id
+ assert is_integer(chain_id)
+ assert chain_id > 0
diff --git a/web3/_utils/module_testing/go_ethereum_admin_module.py b/web3/_utils/module_testing/go_ethereum_admin_module.py
index fb599253..52013020 100644
--- a/web3/_utils/module_testing/go_ethereum_admin_module.py
+++ b/web3/_utils/module_testing/go_ethereum_admin_module.py
@@ -7,8 +7,90 @@ if TYPE_CHECKING:
class GoEthereumAdminModuleTest:
- pass
+ def test_add_peer(self, w3: "Web3") -> None:
+ enode = "enode://f1a6b0bdbf014355587c3018454d070ac57801f05d3b39fe85da574f002a32e929f683d72aa5a8318382e4d3c7a05c9b91687b0d997a39619fb8a6e7ad88e512@1.1.1.1:30303"
+ result = w3.geth.admin.add_peer(enode)
+ assert result
+
+ def test_datadir(self, w3: "Web3") -> None:
+ datadir = w3.geth.admin.datadir()
+ assert isinstance(datadir, str)
+ assert len(datadir) > 0
+
+ def test_node_info(self, w3: "Web3") -> None:
+ node_info = w3.geth.admin.node_info()
+ assert isinstance(node_info, AttributeDict)
+ assert 'enode' in node_info
+ assert 'id' in node_info
+ assert 'ip' in node_info
+ assert 'listenAddr' in node_info
+ assert 'name' in node_info
+
+ def test_peers(self, w3: "Web3") -> None:
+ peers = w3.geth.admin.peers()
+ assert isinstance(peers, List)
+
+ def test_start_rpc(self, w3: "Web3") -> None:
+ result = w3.geth.admin.start_rpc()
+ assert result
+
+ def test_start_ws(self, w3: "Web3") -> None:
+ result = w3.geth.admin.start_ws()
+ assert result
+
+ def test_stop_rpc(self, w3: "Web3") -> None:
+ result = w3.geth.admin.stop_rpc()
+ assert result
+
+ def test_stop_ws(self, w3: "Web3") -> None:
+ result = w3.geth.admin.stop_ws()
+ assert result
class GoEthereumAsyncAdminModuleTest:
- pass
+ @pytest.mark.asyncio
+ async def test_add_peer(self, async_w3: "AsyncWeb3") -> None:
+ enode = "enode://f1a6b0bdbf014355587c3018454d070ac57801f05d3b39fe85da574f002a32e929f683d72aa5a8318382e4d3c7a05c9b91687b0d997a39619fb8a6e7ad88e512@1.1.1.1:30303"
+ result = await async_w3.geth.admin.add_peer(enode)
+ assert result
+
+ @pytest.mark.asyncio
+ async def test_datadir(self, async_w3: "AsyncWeb3") -> None:
+ datadir = await async_w3.geth.admin.datadir()
+ assert isinstance(datadir, str)
+ assert len(datadir) > 0
+
+ @pytest.mark.asyncio
+ async def test_node_info(self, async_w3: "AsyncWeb3") -> None:
+ node_info = await async_w3.geth.admin.node_info()
+ assert isinstance(node_info, AttributeDict)
+ assert 'enode' in node_info
+ assert 'id' in node_info
+ assert 'ip' in node_info
+ assert 'listenAddr' in node_info
+ assert 'name' in node_info
+
+ @pytest.mark.asyncio
+ async def test_peers(self, async_w3: "AsyncWeb3") -> None:
+ peers = await async_w3.geth.admin.peers()
+ assert isinstance(peers, List)
+
+ @pytest.mark.asyncio
+ async def test_start_rpc(self, async_w3: "AsyncWeb3") -> None:
+ result = await async_w3.geth.admin.start_rpc()
+ assert result
+
+ @pytest.mark.asyncio
+ async def test_start_ws(self, async_w3: "AsyncWeb3") -> None:
+ result = await async_w3.geth.admin.start_ws()
+ assert result
+
+ @pytest.mark.asyncio
+ async def test_stop_rpc(self, async_w3: "AsyncWeb3") -> None:
+ result = await async_w3.geth.admin.stop_rpc()
+ assert result
+
+ @pytest.mark.asyncio
+ async def test_stop_ws(self, async_w3: "AsyncWeb3") -> None:
+ result = await async_w3.geth.admin.stop_ws()
+ assert result
diff --git a/web3/_utils/module_testing/go_ethereum_personal_module.py b/web3/_utils/module_testing/go_ethereum_personal_module.py
index 5bc203d7..ccafdab9 100644
--- a/web3/_utils/module_testing/go_ethereum_personal_module.py
+++ b/web3/_utils/module_testing/go_ethereum_personal_module.py
@@ -24,8 +24,118 @@ ACCOUNT_FOR_UNLOCK = '0x12efDc31B1a8FA1A1e756DFD8A1601055C971E13'
class GoEthereumPersonalModuleTest:
- pass
+ def test_personal_import_raw_key(self, web3: "Web3") -> None:
+ actual = web3.geth.personal.import_raw_key(PRIVATE_KEY_HEX, PASSWORD)
+ assert actual == ADDRESS
+
+ def test_personal_list_accounts(self, web3: "Web3") -> None:
+ accounts = web3.geth.personal.list_accounts()
+ assert is_list_like(accounts)
+ assert len(accounts) > 0
+ assert all((is_checksum_address(item) for item in accounts))
+
+ def test_personal_list_wallets(self, web3: "Web3") -> None:
+ wallets = web3.geth.personal.list_wallets()
+ assert is_list_like(wallets)
+ assert len(wallets) > 0
+ assert all((isinstance(item, AttributeDict) for item in wallets))
+ assert all((is_checksum_address(item['accounts'][0]['address']) for item in wallets))
+
+ def test_personal_lock_account(self, web3: "Web3") -> None:
+ # Unlock the account first
+ web3.geth.personal.unlock_account(ACCOUNT_FOR_UNLOCK, PASSWORD)
+ # Now lock it
+ result = web3.geth.personal.lock_account(ACCOUNT_FOR_UNLOCK)
+ assert result is True
+
+ def test_personal_new_account(self, web3: "Web3") -> None:
+ new_account = web3.geth.personal.new_account(PASSWORD)
+ assert is_checksum_address(new_account)
+
+ def test_personal_send_transaction(self, web3: "Web3", unlockable_account_dual_type: ChecksumAddress) -> None:
+ assert unlockable_account_dual_type.startswith('0x')
+
+ tx_params: TxParams = {
+ 'from': unlockable_account_dual_type,
+ 'to': unlockable_account_dual_type,
+ 'value': Wei(1),
+ 'gas': 21000,
+ 'gasPrice': web3.eth.gas_price,
+ }
+ txn_hash = web3.geth.personal.send_transaction(tx_params, PASSWORD)
+ assert txn_hash
+ assert isinstance(txn_hash, HexBytes)
+
+ def test_personal_sign_and_ecrecover(self, web3: "Web3", unlockable_account_dual_type: ChecksumAddress) -> None:
+ message = 'test message'
+ signature = web3.geth.personal.sign(message, unlockable_account_dual_type, PASSWORD)
+ assert isinstance(signature, HexBytes)
+ recovered = web3.geth.personal.ec_recover(message, signature)
+ assert is_same_address(recovered, unlockable_account_dual_type)
+
+ def test_personal_unlock_account(self, web3: "Web3") -> None:
+ result = web3.geth.personal.unlock_account(ACCOUNT_FOR_UNLOCK, PASSWORD)
+ assert result is True
class GoEthereumAsyncPersonalModuleTest:
- pass
+ @pytest.mark.asyncio
+ async def test_async_personal_import_raw_key(self, async_w3: "AsyncWeb3") -> None:
+ actual = await async_w3.geth.personal.import_raw_key(PRIVATE_KEY_HEX, PASSWORD)
+ assert actual == ADDRESS
+
+ @pytest.mark.asyncio
+ async def test_async_personal_list_accounts(self, async_w3: "AsyncWeb3") -> None:
+ accounts = await async_w3.geth.personal.list_accounts()
+ assert is_list_like(accounts)
+ assert len(accounts) > 0
+ assert all((is_checksum_address(item) for item in accounts))
+
+ @pytest.mark.asyncio
+ async def test_async_personal_list_wallets(self, async_w3: "AsyncWeb3") -> None:
+ wallets = await async_w3.geth.personal.list_wallets()
+ assert is_list_like(wallets)
+ assert len(wallets) > 0
+ assert all((isinstance(item, AttributeDict) for item in wallets))
+ assert all((is_checksum_address(item['accounts'][0]['address']) for item in wallets))
+
+ @pytest.mark.asyncio
+ async def test_async_personal_lock_account(self, async_w3: "AsyncWeb3") -> None:
+ # Unlock the account first
+ await async_w3.geth.personal.unlock_account(ACCOUNT_FOR_UNLOCK, PASSWORD)
+ # Now lock it
+ result = await async_w3.geth.personal.lock_account(ACCOUNT_FOR_UNLOCK)
+ assert result is True
+
+ @pytest.mark.asyncio
+ async def test_async_personal_new_account(self, async_w3: "AsyncWeb3") -> None:
+ new_account = await async_w3.geth.personal.new_account(PASSWORD)
+ assert is_checksum_address(new_account)
+
+ @pytest.mark.asyncio
+ async def test_async_personal_send_transaction(self, async_w3: "AsyncWeb3", unlockable_account_dual_type: ChecksumAddress) -> None:
+ assert unlockable_account_dual_type.startswith('0x')
+
+ tx_params: TxParams = {
+ 'from': unlockable_account_dual_type,
+ 'to': unlockable_account_dual_type,
+ 'value': Wei(1),
+ 'gas': 21000,
+ 'gasPrice': await async_w3.eth.gas_price,
+ }
+ txn_hash = await async_w3.geth.personal.send_transaction(tx_params, PASSWORD)
+ assert txn_hash
+ assert isinstance(txn_hash, HexBytes)
+
+ @pytest.mark.asyncio
+ async def test_async_personal_sign_and_ecrecover(self, async_w3: "AsyncWeb3", unlockable_account_dual_type: ChecksumAddress) -> None:
+ message = 'test message'
+ signature = await async_w3.geth.personal.sign(message, unlockable_account_dual_type, PASSWORD)
+ assert isinstance(signature, HexBytes)
+ recovered = await async_w3.geth.personal.ec_recover(message, signature)
+ assert is_same_address(recovered, unlockable_account_dual_type)
+
+ @pytest.mark.asyncio
+ async def test_async_personal_unlock_account(self, async_w3: "AsyncWeb3") -> None:
+ result = await async_w3.geth.personal.unlock_account(ACCOUNT_FOR_UNLOCK, PASSWORD)
+ assert result is True
diff --git a/web3/_utils/module_testing/go_ethereum_txpool_module.py b/web3/_utils/module_testing/go_ethereum_txpool_module.py
index 6c443674..1e916976 100644
--- a/web3/_utils/module_testing/go_ethereum_txpool_module.py
+++ b/web3/_utils/module_testing/go_ethereum_txpool_module.py
@@ -3,8 +3,37 @@ from web3 import AsyncWeb3, Web3
class GoEthereumAsyncTxPoolModuleTest:
- pass
+ @pytest.mark.asyncio
+ async def test_txpool_content(self, async_w3: AsyncWeb3):
+ content = await async_w3.txpool.content()
+ assert isinstance(content, dict)
+ assert all(key in content for key in ('pending', 'queued'))
+
+ @pytest.mark.asyncio
+ async def test_txpool_inspect(self, async_w3: AsyncWeb3):
+ inspect = await async_w3.txpool.inspect()
+ assert isinstance(inspect, dict)
+ assert all(key in inspect for key in ('pending', 'queued'))
+
+ @pytest.mark.asyncio
+ async def test_txpool_status(self, async_w3: AsyncWeb3):
+ status = await async_w3.txpool.status()
+ assert isinstance(status, dict)
+ assert all(key in status for key in ('pending', 'queued'))
class GoEthereumTxPoolModuleTest:
- pass
+ def test_txpool_content(self, w3: Web3):
+ content = w3.txpool.content()
+ assert isinstance(content, dict)
+ assert all(key in content for key in ('pending', 'queued'))
+
+ def test_txpool_inspect(self, w3: Web3):
+ inspect = w3.txpool.inspect()
+ assert isinstance(inspect, dict)
+ assert all(key in inspect for key in ('pending', 'queued'))
+
+ def test_txpool_status(self, w3: Web3):
+ status = w3.txpool.status()
+ assert isinstance(status, dict)
+ assert all(key in status for key in ('pending', 'queued'))
diff --git a/web3/_utils/module_testing/net_module.py b/web3/_utils/module_testing/net_module.py
index b5708289..86a218f0 100644
--- a/web3/_utils/module_testing/net_module.py
+++ b/web3/_utils/module_testing/net_module.py
@@ -6,8 +6,33 @@ if TYPE_CHECKING:
class NetModuleTest:
- pass
+ def test_net_version(self, web3: "Web3") -> None:
+ version = web3.net.version
+ assert is_string(version)
+ assert version.isdigit()
+
+ def test_net_listening(self, web3: "Web3") -> None:
+ listening = web3.net.listening
+ assert is_boolean(listening)
+
+ def test_net_peer_count(self, web3: "Web3") -> None:
+ peer_count = web3.net.peer_count
+ assert is_integer(peer_count)
class AsyncNetModuleTest:
- pass
+ @pytest.mark.asyncio
+ async def test_async_net_version(self, async_w3: "AsyncWeb3") -> None:
+ version = await async_w3.net.version
+ assert is_string(version)
+ assert version.isdigit()
+
+ @pytest.mark.asyncio
+ async def test_async_net_listening(self, async_w3: "AsyncWeb3") -> None:
+ listening = await async_w3.net.listening
+ assert is_boolean(listening)
+
+ @pytest.mark.asyncio
+ async def test_async_net_peer_count(self, async_w3: "AsyncWeb3") -> None:
+ peer_count = await async_w3.net.peer_count
+ assert is_integer(peer_count)
diff --git a/web3/_utils/module_testing/persistent_connection_provider.py b/web3/_utils/module_testing/persistent_connection_provider.py
index 4724376e..2bfd5561 100644
--- a/web3/_utils/module_testing/persistent_connection_provider.py
+++ b/web3/_utils/module_testing/persistent_connection_provider.py
@@ -11,4 +11,74 @@ if TYPE_CHECKING:
class PersistentConnectionProviderTest:
- pass
+ @pytest.mark.asyncio
+ async def test_persistent_connection_provider(self, async_w3: "_PersistentConnectionWeb3") -> None:
+ async_w3.middleware_onion.inject(async_geth_poa_middleware, layer=0)
+
+ async with async_w3.persistent_websocket() as ws:
+ subscription_id = await ws.eth.subscribe("newHeads")
+ assert is_hexstr(subscription_id)
+
+ # Wait for the next block
+ response = await ws.receive()
+ formatted_response = self._format_subscription_response(response)
+
+ assert formatted_response["subscription"] == subscription_id
+ assert "result" in formatted_response
+ assert isinstance(formatted_response["result"], AttributeDict)
+ assert "number" in formatted_response["result"]
+
+ # Unsubscribe
+ success = await ws.eth.unsubscribe(subscription_id)
+ assert success is True
+
+ def _format_subscription_response(
+ self, response: Dict[str, Any]
+ ) -> FormattedEthSubscriptionResponse:
+ return cast(
+ FormattedEthSubscriptionResponse,
+ {
+ "subscription": HexBytes(response["params"]["subscription"]).hex(),
+ "result": AttributeDict(response["params"]["result"]),
+ },
+ )
+
+ @pytest.mark.asyncio
+ async def test_persistent_connection_provider_with_middleware(
+ self, async_w3: "_PersistentConnectionWeb3"
+ ) -> None:
+ async def middleware(make_request, w3):
+ async def middleware_fn(method, params):
+ if method == "eth_blockNumber":
+ return {"result": "0x1234"}
+ return await make_request(method, params)
+ return middleware_fn
+
+ async_w3.middleware_onion.add(middleware)
+
+ async with async_w3.persistent_websocket() as ws:
+ block_number = await ws.eth.block_number
+ assert block_number == 0x1234
+
+ @pytest.mark.asyncio
+ async def test_persistent_connection_provider_multiple_requests(
+ self, async_w3: "_PersistentConnectionWeb3"
+ ) -> None:
+ async with async_w3.persistent_websocket() as ws:
+ tasks = [
+ ws.eth.chain_id,
+ ws.eth.block_number,
+ ws.eth.gas_price,
+ ]
+ results = await asyncio.gather(*tasks)
+
+ assert len(results) == 3
+ assert all(isinstance(result, int) for result in results)
+
+ @pytest.mark.asyncio
+ async def test_persistent_connection_provider_error_handling(
+ self, async_w3: "_PersistentConnectionWeb3"
+ ) -> None:
+ async with async_w3.persistent_websocket() as ws:
+ with pytest.raises(ValueError):
+ await ws.eth.get_block("invalid_block_identifier")
diff --git a/web3/_utils/module_testing/web3_module.py b/web3/_utils/module_testing/web3_module.py
index fbc05357..89846a84 100644
--- a/web3/_utils/module_testing/web3_module.py
+++ b/web3/_utils/module_testing/web3_module.py
@@ -8,4 +8,51 @@ from web3.exceptions import InvalidAddress
class Web3ModuleTest:
- pass
+ def test_web3_clientVersion(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ client_version = web3.clientVersion
+ assert isinstance(client_version, str)
+ assert len(client_version) > 0
+
+ def test_web3_api_deprecated(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ with pytest.warns(DeprecationWarning):
+ assert web3.api == "web3"
+
+ def test_web3_sha3(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.sha3(0x678901) == HexBytes('0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45')
+ assert web3.sha3(text='web3.py') == HexBytes('0x64e604787cbf194841e7b68d7cd28786f6c9a0a3ab9f8b0a0e87cb4387ab0107')
+ assert web3.sha3(hexstr='0x80') == HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421')
+ assert web3.sha3(b'\x01\x02\x03') == HexBytes('0x6e519b1ba5fabfa25f89f1dbe6037ec016d3dc33c0a6e4f56a5f33a93b99db80')
+
+ def test_web3_to_hex(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.to_hex(b'\x01\x02\x03') == '0x010203'
+ assert web3.to_hex('abc') == '0x616263'
+ assert web3.to_hex(12345) == '0x3039'
+
+ def test_web3_to_text(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.to_text(HexBytes('0x666f6f626172')) == 'foobar'
+ assert web3.to_text('0x666f6f626172') == 'foobar'
+ assert web3.to_text(b'foobar') == 'foobar'
+
+ def test_web3_to_bytes(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.to_bytes(HexBytes('0x666f6f626172')) == b'foobar'
+ assert web3.to_bytes('0x666f6f626172') == b'foobar'
+ assert web3.to_bytes(b'foobar') == b'foobar'
+
+ def test_web3_is_address(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.is_address('0xd3CdA913deB6f67967B99D67aCDFa1712C293601')
+ assert not web3.is_address('0xd3CdA913deB6f67967B99D67aCDFa1712C293601#')
+ assert not web3.is_address('0xd3CdA913deB6f67967B99D67aCDFa1712C293601#')
+ assert not web3.is_address('0xd3CdA913deB6f67967B99D67aCDFa1712C293601@')
+
+ def test_web3_is_checksum_address(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.is_checksum_address('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed')
+ assert not web3.is_checksum_address('0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed')
+ assert not web3.is_checksum_address('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed#')
+
+ def test_web3_to_checksum_address(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.to_checksum_address('0xd3cda913deb6f67967b99d67acdfa1712c293601') == '0xd3CdA913deB6f67967B99D67aCDFa1712C293601'
+ with pytest.raises(InvalidAddress):
+ web3.to_checksum_address('0xd3cda913deb6f67967b99d67acdfa1712c293601#')
+
+ def test_web3_is_connected(self, web3: Union[Web3, AsyncWeb3]) -> None:
+ assert web3.is_connected()
diff --git a/web3/_utils/normalizers.py b/web3/_utils/normalizers.py
index 8cae7f1d..bc7a7ce4 100644
--- a/web3/_utils/normalizers.py
+++ b/web3/_utils/normalizers.py
@@ -26,7 +26,16 @@ def parse_basic_type_str(old_normalizer: Callable[[BasicType, TypeStr, Any],
that type string does not represent a basic type (i.e. non-tuple type) or is
not parsable, the normalizer does nothing.
"""
- pass
+ @functools.wraps(old_normalizer)
+ def new_normalizer(type_str: TypeStr, data: Any) -> Tuple[TypeStr, Any]:
+ try:
+ abi_type = parse(type_str)
+ if isinstance(abi_type, BasicType):
+ return old_normalizer(abi_type, type_str, data)
+ except ParseError:
+ pass
+ return type_str, data
+ return new_normalizer
BASE_RETURN_NORMALIZERS = [addresses_checksummed]
diff --git a/web3/_utils/transactions.py b/web3/_utils/transactions.py
index 50219835..f73fa32c 100644
--- a/web3/_utils/transactions.py
+++ b/web3/_utils/transactions.py
@@ -29,4 +29,27 @@ def fill_transaction_defaults(w3: 'Web3', transaction: TxParams) ->TxParams:
"""
if w3 is None, fill as much as possible while offline
"""
- pass
+ filled_transaction = cast(TxParams, {})
+
+ if transaction.get('type') in DYNAMIC_FEE_TXN_PARAMS:
+ # For dynamic fee transactions (EIP-1559)
+ defaults = ['maxFeePerGas', 'maxPriorityFeePerGas', 'gas', 'value', 'data', 'chainId']
+ else:
+ # For legacy transactions
+ defaults = ['gasPrice', 'gas', 'value', 'data', 'chainId']
+
+ for key, default_val in TRANSACTION_DEFAULTS.items():
+ if key in defaults:
+ if key not in transaction:
+ if callable(default_val):
+ if w3 is not None:
+ filled_transaction[key] = default_val(w3, transaction)
+ else:
+ filled_transaction[key] = default_val
+
+ filled_transaction = merge(filled_transaction, transaction)
+
+ if 'gas' in filled_transaction and filled_transaction['gas'] is None:
+ filled_transaction = assoc(filled_transaction, 'gas', w3.eth.estimate_gas(filled_transaction))
+
+ return filled_transaction
diff --git a/web3/_utils/type_conversion.py b/web3/_utils/type_conversion.py
index ac15970d..79635b8d 100644
--- a/web3/_utils/type_conversion.py
+++ b/web3/_utils/type_conversion.py
@@ -8,7 +8,9 @@ def to_hex_if_bytes(val: Union[HexStr, str, bytes, bytearray]) ->HexStr:
Note: This method does not validate against all cases and is only
meant to work with bytes and hex strings.
"""
- pass
+ if isinstance(val, (bytes, bytearray)):
+ return to_hex(val)
+ return HexStr(val)
def to_bytes_if_hex(val: Union[HexStr, str, bytes, bytearray]) ->bytes:
@@ -16,4 +18,6 @@ def to_bytes_if_hex(val: Union[HexStr, str, bytes, bytearray]) ->bytes:
Note: This method does not validate against all cases and is only
meant to work with bytes and hex strings.
"""
- pass
+ if isinstance(val, str):
+ return to_bytes(hexstr=val)
+ return to_bytes(val)
diff --git a/web3/_utils/utility_methods.py b/web3/_utils/utility_methods.py
index e3a04eb6..fa7c1d1d 100644
--- a/web3/_utils/utility_methods.py
+++ b/web3/_utils/utility_methods.py
@@ -13,7 +13,7 @@ def all_in_dict(values: Iterable[Any], d: Union[Dict[Any, Any], TxData,
:return: True if ALL values exist in keys;
False if NOT ALL values exist in keys
"""
- pass
+ return all(value in d for value in values)
def any_in_dict(values: Iterable[Any], d: Union[Dict[Any, Any], TxData,
@@ -27,7 +27,7 @@ def any_in_dict(values: Iterable[Any], d: Union[Dict[Any, Any], TxData,
:return: True if ANY value exists in keys;
False if NONE of the values exist in keys
"""
- pass
+ return any(value in d for value in values)
def none_in_dict(values: Iterable[Any], d: Union[Dict[Any, Any], TxData,
@@ -41,7 +41,7 @@ def none_in_dict(values: Iterable[Any], d: Union[Dict[Any, Any], TxData,
:return: True if NONE of the values exist in keys;
False if ANY value exists in keys
"""
- pass
+ return not any(value in d for value in values)
def either_set_is_a_subset(set1: Set[Any], set2: Set[Any], percentage: int=100
@@ -58,4 +58,8 @@ def either_set_is_a_subset(set1: Set[Any], set2: Set[Any], percentage: int=100
:return: True if one set's intersection with the other set is greater
than or equal to the given percentage of the other set.
"""
- pass
+ if not set1 or not set2:
+ return False
+ intersection = set1.intersection(set2)
+ return (len(intersection) >= len(set1) * percentage / 100 or
+ len(intersection) >= len(set2) * percentage / 100)
diff --git a/web3/_utils/validation.py b/web3/_utils/validation.py
index 9b02dbf1..9fcacc43 100644
--- a/web3/_utils/validation.py
+++ b/web3/_utils/validation.py
@@ -8,6 +8,22 @@ from eth_utils.toolz import compose, groupby, valfilter, valmap
from ens.utils import is_valid_ens_name
from web3._utils.abi import abi_to_signature, filter_by_type, is_address_type, is_array_type, is_bool_type, is_bytes_type, is_int_type, is_recognized_type, is_string_type, is_uint_type, length_of_array_type, sub_type_of_array_type
from web3.exceptions import InvalidAddress
+
+def is_address(value: Any) -> bool:
+ """
+ Check if the given value is a valid Ethereum address.
+ """
+ if not isinstance(value, str):
+ return False
+ if is_checksum_address(value):
+ return True
+ if is_hex_address(value):
+ return True
+ if is_binary_address(value):
+ return True
+ if is_valid_ens_name(value):
+ return True
+ return False
from web3.types import ABI, ABIFunction
@@ -15,14 +31,21 @@ def validate_abi(abi: ABI) ->None:
"""
Helper function for validating an ABI
"""
- pass
+ if not isinstance(abi, list):
+ raise ValueError("ABI must be a list")
+ for item in abi:
+ if not isinstance(item, dict):
+ raise ValueError("ABI items must be dictionaries")
+ if "type" not in item:
+ raise ValueError("ABI item must have a 'type' key")
def validate_abi_type(abi_type: TypeStr) ->None:
"""
Helper function for validating an abi_type
"""
- pass
+ if not is_recognized_type(abi_type):
+ raise ValueError(f"Invalid ABI type: {abi_type}")
def validate_abi_value(abi_type: TypeStr, value: Any) ->None:
@@ -30,11 +53,33 @@ def validate_abi_value(abi_type: TypeStr, value: Any) ->None:
Helper function for validating a value against the expected abi_type
Note: abi_type 'bytes' must either be python3 'bytes' object or ''
"""
- pass
+ if is_array_type(abi_type):
+ sub_type = sub_type_of_array_type(abi_type)
+ if not is_list_like(value):
+ raise ValueError(f"Expected list for array type: {abi_type}")
+ for item in value:
+ validate_abi_value(sub_type, item)
+ elif is_bool_type(abi_type):
+ if not is_boolean(value):
+ raise ValueError(f"Expected boolean for type: {abi_type}")
+ elif is_int_type(abi_type) or is_uint_type(abi_type):
+ if not is_integer(value):
+ raise ValueError(f"Expected integer for type: {abi_type}")
+ elif is_address_type(abi_type):
+ validate_address(value)
+ elif is_bytes_type(abi_type):
+ if not (is_bytes(value) or value == ''):
+ raise ValueError(f"Expected bytes or empty string for type: {abi_type}")
+ elif is_string_type(abi_type):
+ if not is_string(value):
+ raise ValueError(f"Expected string for type: {abi_type}")
+ else:
+ raise ValueError(f"Unsupported ABI type: {abi_type}")
def validate_address(value: Any) ->None:
"""
Helper function for validating an address
"""
- pass
+ if not is_address(value):
+ raise InvalidAddress(f"'{value}' is not a valid address")
diff --git a/web3/contract/async_contract.py b/web3/contract/async_contract.py
index 257ec0b8..574e70a7 100644
--- a/web3/contract/async_contract.py
+++ b/web3/contract/async_contract.py
@@ -86,7 +86,33 @@ class AsyncContractEvent(BaseContractEvent):
same time as fromBlock or toBlock
:yield: Tuple of :class:`AttributeDict` instances
"""
- pass
+ if block_hash is not None and (fromBlock is not None or toBlock is not None):
+ raise ValueError("Cannot set both block_hash and fromBlock/toBlock")
+
+ abi = self._get_event_abi()
+ argument_filters = argument_filters or {}
+
+ if fromBlock is None:
+ fromBlock = "latest"
+ if toBlock is None:
+ toBlock = "latest"
+
+ # Construct the filter
+ filter_params = {
+ "address": self.address,
+ "topics": self.get_event_topics(argument_filters),
+ }
+
+ if block_hash is not None:
+ filter_params["blockHash"] = block_hash
+ else:
+ filter_params["fromBlock"] = fromBlock
+ filter_params["toBlock"] = toBlock
+
+ logs = await self.w3.eth.get_logs(filter_params)
+
+ # Convert raw log data into Python objects
+ return [get_event_data(self.w3.codec, abi, entry) for entry in logs]
@combomethod
async def create_filter(self, *, argument_filters: Optional[Dict[str,
@@ -96,7 +122,26 @@ class AsyncContractEvent(BaseContractEvent):
"""
Create filter object that tracks logs emitted by this contract event.
"""
- pass
+ abi = self._get_event_abi()
+ self._validate_filter_params(argument_filters)
+
+ _filters = dict(**argument_filters) if argument_filters else {}
+
+ data_filter_set, event_filter_params = construct_event_filter_params(
+ abi,
+ self.w3.codec,
+ contract_address=self.address,
+ argument_filters=_filters,
+ fromBlock=fromBlock,
+ toBlock=toBlock,
+ address=address,
+ topics=topics,
+ )
+
+ filter_builder = AsyncEventFilterBuilder(self.w3, self.address, self._get_event_abi())
+ return await filter_builder.create_filter(
+ data_filter_set, event_filter_params
+ )
class AsyncContractEvents(BaseContractEvents):
@@ -152,7 +197,24 @@ class AsyncContractFunction(BaseContractFunction):
:return: ``Caller`` object that has contract public functions
and variables exposed as Python methods
"""
- pass
+ call_transaction = self._get_call_txn(transaction)
+
+ block_id = await async_parse_block_identifier(self.w3, block_identifier)
+
+ return await async_call_contract_function(
+ self.w3,
+ self.address,
+ self._return_data_normalizers,
+ self.function_identifier,
+ call_transaction,
+ block_id,
+ self.contract_abi,
+ self.abi,
+ state_override,
+ ccip_read_enabled,
+ *self.args,
+ **self.kwargs
+ )
class AsyncContractFunctions(BaseContractFunctions):
@@ -213,7 +275,13 @@ class AsyncContract(BaseContract):
:param kwargs: The contract constructor arguments as keyword arguments
:return: a contract constructor object
"""
- pass
+ if cls.bytecode is None:
+ raise ValueError(
+ "Cannot call constructor on a contract that does not have 'bytecode' associated "
+ "with it"
+ )
+
+ return AsyncContractConstructor(cls.w3, cls.abi, cls.bytecode, *args, **kwargs)
class AsyncContractCaller(BaseContractCaller):
@@ -260,4 +328,20 @@ class AsyncContractConstructor(BaseContractConstructor):
"""
Build the transaction dictionary without sending
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ defaults = await async_get_transaction_defaults(self.w3, transaction)
+
+ if self.address is not None:
+ defaults['to'] = self.address
+
+ if self.w3.eth.default_account is not empty:
+ defaults['from'] = self.w3.eth.default_account
+
+ if 'data' in defaults:
+ raise ValueError("Cannot set data in constructor transaction")
+
+ defaults['data'] = self._encode_data()
+
+ return await async_fill_transaction_defaults(self.w3, defaults)
diff --git a/web3/contract/base_contract.py b/web3/contract/base_contract.py
index 49dd148e..af3b043d 100644
--- a/web3/contract/base_contract.py
+++ b/web3/contract/base_contract.py
@@ -213,7 +213,18 @@ class BaseContract:
:param data: defaults to function selector
"""
- pass
+ if args is None:
+ args = tuple()
+ if kwargs is None:
+ kwargs = {}
+
+ fn_abi = find_matching_fn_abi(cls.abi, fn_name, args, kwargs)
+ arguments = merge_args_and_kwargs(fn_abi, args, kwargs)
+
+ if data is None:
+ data = add_0x_prefix(function_abi_to_4byte_selector(fn_abi))
+
+ return add_0x_prefix(encode_abi(cls.w3, fn_abi, arguments, data))
_return_data_normalizers: Tuple[Callable[..., Any], ...] = tuple()
diff --git a/web3/contract/contract.py b/web3/contract/contract.py
index 5e82a39a..6c7d680d 100644
--- a/web3/contract/contract.py
+++ b/web3/contract/contract.py
@@ -86,7 +86,34 @@ class ContractEvent(BaseContractEvent):
same time as fromBlock or toBlock
:yield: Tuple of :class:`AttributeDict` instances
"""
- pass
+ if block_hash is not None and (fromBlock is not None or toBlock is not None):
+ raise ValueError("block_hash cannot be used with fromBlock or toBlock")
+
+ abi = self._get_event_abi()
+ argument_filters = argument_filters or {}
+
+ if fromBlock is None:
+ fromBlock = "latest"
+ if toBlock is None:
+ toBlock = "latest"
+
+ # Construct the filter
+ filter_params = {
+ "address": self.address,
+ "topics": self._get_event_filter_topics(abi, argument_filters),
+ }
+
+ if block_hash is not None:
+ filter_params["blockHash"] = block_hash
+ else:
+ filter_params["fromBlock"] = fromBlock
+ filter_params["toBlock"] = toBlock
+
+ logs = self.w3.eth.get_logs(filter_params)
+
+ # Convert raw log data into processed event data
+ for log in logs:
+ yield get_event_data(self.w3, abi, log)
@combomethod
def create_filter(self, *, argument_filters: Optional[Dict[str, Any]]=
@@ -96,7 +123,30 @@ class ContractEvent(BaseContractEvent):
"""
Create filter object that tracks logs emitted by this contract event.
"""
- pass
+ abi = self._get_event_abi()
+
+ argument_filters = argument_filters or {}
+ _filters = dict(**argument_filters)
+
+ data_filter_set, event_filter_params = construct_event_filter_params(
+ abi,
+ contract_address=self.address,
+ argument_filters=_filters,
+ fromBlock=fromBlock,
+ toBlock=toBlock,
+ address=address,
+ topics=topics,
+ )
+
+ log_data_extract_fn = functools.partial(get_event_data, abi)
+
+ log_filter = self.w3.eth.filter(event_filter_params)
+
+ log_filter.set_data_filters(data_filter_set)
+ log_filter.log_entry_formatter = log_data_extract_fn
+ log_filter.filter_params = event_filter_params
+
+ return log_filter
class ContractEvents(BaseContractEvents):
@@ -152,7 +202,24 @@ class ContractFunction(BaseContractFunction):
:return: ``Caller`` object that has contract public functions
and variables exposed as Python methods
"""
- pass
+ call_transaction = self._get_call_txn(transaction)
+
+ block_id = parse_block_identifier(self.w3, block_identifier)
+
+ return call_contract_function(
+ self.w3,
+ self.address,
+ self._return_data_normalizers,
+ self.function_identifier,
+ call_transaction,
+ block_id,
+ self.contract_abi,
+ self.abi,
+ state_override,
+ ccip_read_enabled,
+ *self.args,
+ **self.kwargs
+ )
class ContractFunctions(BaseContractFunctions):
@@ -212,7 +279,19 @@ class Contract(BaseContract):
:param kwargs: The contract constructor arguments as keyword arguments
:return: a contract constructor object
"""
- pass
+ if cls.bytecode is None:
+ raise ValueError(
+ "Cannot call constructor on a contract that does not have 'bytecode' associated "
+ "with it"
+ )
+
+ return ContractConstructor(
+ cls.w3,
+ cls.abi,
+ cls.bytecode,
+ *args,
+ **kwargs
+ )
class ContractCaller(BaseContractCaller):
@@ -258,4 +337,15 @@ class ContractConstructor(BaseContractConstructor):
"""
Build the transaction dictionary without sending
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ contracts = self.w3.eth.contract(
+ abi=self.abi, bytecode=self.bytecode, bytecode_runtime=self.bytecode_runtime
+ )
+
+ built_transaction = contracts._encode_constructor_data(
+ transaction, self.args, self.kwargs
+ )
+
+ return fill_transaction_defaults(self.w3, built_transaction)
diff --git a/web3/contract/utils.py b/web3/contract/utils.py
index 5bb9317a..6068c62d 100644
--- a/web3/contract/utils.py
+++ b/web3/contract/utils.py
@@ -26,7 +26,65 @@ def call_contract_function(w3: 'Web3', address: ChecksumAddress,
Helper function for interacting with a contract function using the
`eth_call` API.
"""
- pass
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs)
+
+ if transaction is None:
+ transaction = {}
+
+ processed_transaction = prepare_transaction(
+ address,
+ w3,
+ fn_identifier=function_identifier,
+ contract_abi=contract_abi,
+ fn_abi=fn_abi,
+ transaction=transaction,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ call_transaction = fill_transaction_defaults(w3, processed_transaction)
+
+ output_types = get_abi_output_types(fn_abi)
+
+ call_params = {
+ 'to': address,
+ 'data': call_transaction['data'],
+ }
+ if 'gas' in call_transaction:
+ call_params['gas'] = call_transaction['gas']
+
+ if state_override is not None:
+ call_params['state_override'] = state_override
+
+ if ccip_read_enabled is not None:
+ call_params['ccip_read_enabled'] = ccip_read_enabled
+
+ result = w3.eth.call(call_params, block_identifier=block_id)
+
+ try:
+ output_data = w3.codec.decode(output_types, result)
+ except DecodingError as e:
+ # Provide a more helpful error message than the one provided by
+ # eth-abi-utils
+ msg = (
+ "Could not decode contract function call {} return data {} for "
+ "output_types {}".format(
+ function_identifier,
+ result,
+ output_types
+ )
+ )
+ raise BadFunctionCallOutput(msg) from e
+
+ normalized_data = map_abi_data(normalizers, output_types, output_data)
+
+ if len(normalized_data) == 1:
+ return normalized_data[0]
+ elif decode_tuples:
+ return named_tree(normalized_data, output_types)
+ else:
+ return normalized_data
def transact_with_contract_function(address: ChecksumAddress, w3: 'Web3',
@@ -37,7 +95,28 @@ def transact_with_contract_function(address: ChecksumAddress, w3: 'Web3',
Helper function for interacting with a contract function by sending a
transaction.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_name, args, kwargs)
+
+ if function_name is None:
+ function_name = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ w3,
+ fn_identifier=function_name,
+ contract_abi=contract_abi,
+ transaction=transaction,
+ fn_abi=fn_abi,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ txn_hash = w3.eth.send_transaction(processed_transaction)
+ return txn_hash
def estimate_gas_for_function(address: ChecksumAddress, w3: 'Web3',
@@ -51,7 +130,33 @@ def estimate_gas_for_function(address: ChecksumAddress, w3: 'Web3',
Don't call this directly, instead use :meth:`Contract.estimate_gas`
on your contract instance.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, fn_identifier, args, kwargs)
+
+ if fn_identifier is None:
+ fn_identifier = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ w3,
+ fn_identifier=fn_identifier,
+ contract_abi=contract_abi,
+ fn_abi=fn_abi,
+ transaction=transaction,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ gas_estimate = w3.eth.estimate_gas(
+ processed_transaction,
+ block_identifier=block_identifier,
+ state_override=state_override
+ )
+
+ return gas_estimate
def build_transaction_for_function(address: ChecksumAddress, w3: 'Web3',
@@ -63,7 +168,27 @@ def build_transaction_for_function(address: ChecksumAddress, w3: 'Web3',
Don't call this directly, instead use :meth:`Contract.build_transaction`
on your contract instance.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_name, args, kwargs)
+
+ if function_name is None:
+ function_name = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ w3,
+ fn_identifier=function_name,
+ contract_abi=contract_abi,
+ transaction=transaction,
+ fn_abi=fn_abi,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ return fill_transaction_defaults(w3, processed_transaction)
async def async_call_contract_function(async_w3: 'AsyncWeb3', address:
@@ -77,7 +202,63 @@ async def async_call_contract_function(async_w3: 'AsyncWeb3', address:
Helper function for interacting with a contract function using the
`eth_call` API.
"""
- pass
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs)
+
+ if transaction is None:
+ transaction = {}
+
+ processed_transaction = prepare_transaction(
+ address,
+ async_w3,
+ fn_identifier=function_identifier,
+ contract_abi=contract_abi,
+ fn_abi=fn_abi,
+ transaction=transaction,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ call_transaction = await async_fill_transaction_defaults(async_w3, processed_transaction)
+
+ output_types = get_abi_output_types(fn_abi)
+
+ call_params = {
+ 'to': address,
+ 'data': call_transaction['data'],
+ }
+ if 'gas' in call_transaction:
+ call_params['gas'] = call_transaction['gas']
+
+ if state_override is not None:
+ call_params['state_override'] = state_override
+
+ if ccip_read_enabled is not None:
+ call_params['ccip_read_enabled'] = ccip_read_enabled
+
+ result = await async_w3.eth.call(call_params, block_identifier=block_id)
+
+ try:
+ output_data = async_w3.codec.decode(output_types, result)
+ except DecodingError as e:
+ msg = (
+ "Could not decode contract function call {} return data {} for "
+ "output_types {}".format(
+ function_identifier,
+ result,
+ output_types
+ )
+ )
+ raise BadFunctionCallOutput(msg) from e
+
+ normalized_data = map_abi_data(normalizers, output_types, output_data)
+
+ if len(normalized_data) == 1:
+ return normalized_data[0]
+ elif decode_tuples:
+ return named_tree(normalized_data, output_types)
+ else:
+ return normalized_data
async def async_transact_with_contract_function(address: ChecksumAddress,
@@ -88,7 +269,28 @@ async def async_transact_with_contract_function(address: ChecksumAddress,
Helper function for interacting with a contract function by sending a
transaction.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_name, args, kwargs)
+
+ if function_name is None:
+ function_name = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ async_w3,
+ fn_identifier=function_name,
+ contract_abi=contract_abi,
+ transaction=transaction,
+ fn_abi=fn_abi,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ txn_hash = await async_w3.eth.send_transaction(processed_transaction)
+ return txn_hash
async def async_estimate_gas_for_function(address: ChecksumAddress,
@@ -102,7 +304,33 @@ async def async_estimate_gas_for_function(address: ChecksumAddress,
Don't call this directly, instead use :meth:`Contract.estimate_gas`
on your contract instance.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, fn_identifier, args, kwargs)
+
+ if fn_identifier is None:
+ fn_identifier = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ async_w3,
+ fn_identifier=fn_identifier,
+ contract_abi=contract_abi,
+ fn_abi=fn_abi,
+ transaction=transaction,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ gas_estimate = await async_w3.eth.estimate_gas(
+ processed_transaction,
+ block_identifier=block_identifier,
+ state_override=state_override
+ )
+
+ return gas_estimate
async def async_build_transaction_for_function(address: ChecksumAddress,
@@ -114,4 +342,24 @@ async def async_build_transaction_for_function(address: ChecksumAddress,
Don't call this directly, instead use :meth:`Contract.build_transaction`
on your contract instance.
"""
- pass
+ if transaction is None:
+ transaction = {}
+
+ if fn_abi is None:
+ fn_abi = find_matching_fn_abi(contract_abi, function_name, args, kwargs)
+
+ if function_name is None:
+ function_name = fn_abi['name']
+
+ processed_transaction = prepare_transaction(
+ address,
+ async_w3,
+ fn_identifier=function_name,
+ contract_abi=contract_abi,
+ transaction=transaction,
+ fn_abi=fn_abi,
+ fn_args=args,
+ fn_kwargs=kwargs,
+ )
+
+ return await async_fill_transaction_defaults(async_w3, processed_transaction)
diff --git a/web3/datastructures.py b/web3/datastructures.py
index 0a0a115e..4cf0674b 100644
--- a/web3/datastructures.py
+++ b/web3/datastructures.py
@@ -35,7 +35,18 @@ class ReadableAttributeDict(Mapping[TKey, TValue]):
Custom pretty output for the IPython console
https://ipython.readthedocs.io/en/stable/api/generated/IPython.lib.pretty.html#extending # noqa: E501
"""
- pass
+ class_name = self.__class__.__name__
+ if cycle:
+ builder.text(f'{class_name}(...)')
+ else:
+ with builder.group(4, f'{class_name}(', ')'):
+ for idx, (key, value) in enumerate(self.__dict__.items()):
+ if idx:
+ builder.text(',')
+ builder.breakable()
+ builder.pretty(key)
+ builder.text(': ')
+ builder.pretty(value)
class MutableAttributeDict(MutableMapping[TKey, TValue],
@@ -83,7 +94,14 @@ def tupleize_lists_nested(d: Mapping[TKey, TValue]) ->AttributeDict[TKey,
This method converts lists to tuples, rendering them hashable.
Other unhashable types found will raise a TypeError
"""
- pass
+ def _tupleize_lists(item: Any) -> Any:
+ if isinstance(item, list):
+ return tuple(_tupleize_lists(i) for i in item)
+ elif isinstance(item, dict):
+ return {k: _tupleize_lists(v) for k, v in item.items()}
+ return item
+
+ return AttributeDict({k: _tupleize_lists(v) for k, v in d.items()})
class NamedElementOnion(Mapping[TKey, TValue]):
@@ -111,7 +129,16 @@ class NamedElementOnion(Mapping[TKey, TValue]):
or at the outermost layer. Note that inserting to the outermost is equivalent
to calling :meth:`add` .
"""
- pass
+ if layer is None:
+ # Insert at the outermost layer (equivalent to add)
+ self.add(element, name)
+ elif layer == 0:
+ # Insert at the innermost layer
+ if name is None:
+ name = element.__name__ if hasattr(element, '__name__') else str(element)
+ self._queue[name] = element
+ else:
+ raise ValueError("Only layer=None (outermost) or layer=0 (innermost) are supported")
@property
def middlewares(self) ->Sequence[Any]:
@@ -119,7 +146,7 @@ class NamedElementOnion(Mapping[TKey, TValue]):
Returns middlewares in the appropriate order to be imported into a new Web3
instance (reversed _queue order) as a list of (middleware, name) tuples.
"""
- pass
+ return [(middleware, name) for name, middleware in reversed(self._queue.items())]
def __iter__(self) ->Iterator[TKey]:
elements = self._queue.values()
diff --git a/web3/eth/async_eth.py b/web3/eth/async_eth.py
index a6135aed..a951d81d 100644
--- a/web3/eth/async_eth.py
+++ b/web3/eth/async_eth.py
@@ -16,6 +16,7 @@ from web3.exceptions import MethodUnavailable, OffchainLookup, TimeExhausted, To
from web3.method import Method, default_root_munger
from web3.providers import PersistentConnectionProvider
from web3.types import ENS, BlockData, BlockIdentifier, BlockParams, CallOverride, CreateAccessListResponse, FeeHistory, FilterParams, LogReceipt, LogsSubscriptionArg, Nonce, SignedTx, SubscriptionType, SyncStatus, TxData, TxParams, TxReceipt, Wei, _Hash32
+from web3._utils.fee_utils import async_fee_history_priority_fee
from web3.utils import async_handle_offchain_lookup
if TYPE_CHECKING:
from web3 import AsyncWeb3
@@ -42,13 +43,31 @@ class AsyncEth(BaseEth):
eth_maxPriorityFeePerGas, is_property=True)
@property
- async def max_priority_fee(self) ->Wei:
+ async def max_priority_fee(self) -> Wei:
"""
Try to use eth_maxPriorityFeePerGas but, since this is not part
of the spec and is only supported by some clients, fall back to
an eth_feeHistory calculation with min and max caps.
"""
- pass
+ try:
+ return await self._max_priority_fee()
+ except (ValueError, MethodUnavailable):
+ return await self._suggest_max_priority_fee()
+
+ async def _suggest_max_priority_fee(self) -> Wei:
+ # Use fee history to estimate priority fee
+ fee_history = await self._fee_history(10, 'latest', [10])
+ latest_block = await self.get_block('latest')
+ base_fee = latest_block['baseFeePerGas']
+
+ # Calculate priority fee based on fee history
+ priority_fee = await async_fee_history_priority_fee(fee_history)
+
+ # Apply min and max caps
+ min_priority_fee = Wei(1_000_000_000) # 1 gwei
+ max_priority_fee = Wei(2_000_000_000) # 2 gwei
+
+ return Wei(max(min(priority_fee, max_priority_fee), min_priority_fee))
_mining: Method[Callable[[], Awaitable[bool]]] = Method(RPC.eth_mining,
is_property=True)
_syncing: Method[Callable[[], Awaitable[Union[SyncStatus, bool]]]
diff --git a/web3/eth/eth.py b/web3/eth/eth.py
index 3469bcec..379b5d81 100644
--- a/web3/eth/eth.py
+++ b/web3/eth/eth.py
@@ -38,13 +38,28 @@ class Eth(BaseEth):
eth_maxPriorityFeePerGas, is_property=True)
@property
- def max_priority_fee(self) ->Wei:
+ def max_priority_fee(self) -> Wei:
"""
Try to use eth_maxPriorityFeePerGas but, since this is not part
of the spec and is only supported by some clients, fall back to
an eth_feeHistory calculation with min and max caps.
"""
- pass
+ try:
+ return self._max_priority_fee()
+ except (ValueError, MethodUnavailable):
+ # If eth_maxPriorityFeePerGas is not available, fall back to fee history calculation
+ block_count = 10
+ latest_block = self.get_block_number()
+ fee_history = self._fee_history(block_count, latest_block, [10])
+
+ priority_fees = [int(fee[0], 16) for fee in fee_history['reward']]
+ average_priority_fee = sum(priority_fees) // len(priority_fees)
+
+ # Apply min and max caps (you may want to adjust these values)
+ min_priority_fee = Wei(1_000_000_000) # 1 Gwei
+ max_priority_fee = Wei(100_000_000_000) # 100 Gwei
+
+ return Wei(max(min_priority_fee, min(average_priority_fee, max_priority_fee)))
_mining: Method[Callable[[], bool]] = Method(RPC.eth_mining,
is_property=True)
_syncing: Method[Callable[[], Union[SyncStatus, bool]]] = Method(RPC.
diff --git a/web3/gas_strategies/rpc.py b/web3/gas_strategies/rpc.py
index e4bf92f5..d2b891d1 100644
--- a/web3/gas_strategies/rpc.py
+++ b/web3/gas_strategies/rpc.py
@@ -3,9 +3,8 @@ from web3 import Web3
from web3.types import TxParams, Wei
-def rpc_gas_price_strategy(w3: Web3, transaction_params: Optional[TxParams]
- =None) ->Wei:
+def rpc_gas_price_strategy(w3: Web3, transaction_params: Optional[TxParams] = None) -> Wei:
"""
- A simple gas price strategy deriving it's value from the eth_gasPrice JSON-RPC call.
+ A simple gas price strategy deriving its value from the eth_gasPrice JSON-RPC call.
"""
- pass
+ return w3.eth.gas_price
diff --git a/web3/gas_strategies/time_based.py b/web3/gas_strategies/time_based.py
index 653c12e4..3865d45a 100644
--- a/web3/gas_strategies/time_based.py
+++ b/web3/gas_strategies/time_based.py
@@ -22,7 +22,11 @@ def _compute_probabilities(miner_data: Iterable[MinerData], wait_blocks:
Computes the probabilities that a txn will be accepted at each of the gas
prices accepted by the miners.
"""
- pass
+ miner_data = sorted(miner_data, key=operator.attrgetter('min_gas_price'))
+ for idx, data in enumerate(miner_data):
+ num_blocks_accepting = sum(m.num_blocks for m in miner_data[idx:])
+ probability = float(min(num_blocks_accepting, wait_blocks)) / float(sample_size)
+ yield Probability(data.min_gas_price, probability)
def _compute_gas_price(probabilities: Sequence[Probability],
@@ -37,7 +41,15 @@ def _compute_gas_price(probabilities: Sequence[Probability],
:param desired_probability: An floating point representation of the desired
probability. (e.g. ``85% -> 0.85``)
"""
- pass
+ for left, right in sliding_window(2, probabilities):
+ if desired_probability <= right.prob:
+ return Wei(int(left.gas_price))
+ elif right.prob < desired_probability < left.prob:
+ prob_range = left.prob - right.prob
+ price_range = left.gas_price - right.gas_price
+ prob_delta = desired_probability - right.prob
+ return Wei(int(right.gas_price + (prob_delta * price_range / prob_range)))
+ return Wei(int(probabilities[-1].gas_price))
@curry
@@ -57,7 +69,73 @@ def construct_time_based_gas_price_strategy(max_wait_seconds: int,
that the transaction will be mined within ``max_wait_seconds``. 0 means 0%
and 100 means 100%.
"""
- pass
+ def time_based_gas_price_strategy(web3: Web3, transaction_params: TxParams) -> Wei:
+ if probability < 0 or probability > 100:
+ raise Web3ValidationError(
+ "The `probability` value must be a number between 0 and 100"
+ )
+
+ if sample_size < 2:
+ raise Web3ValidationError(
+ "The `sample_size` value must be at least 2"
+ )
+
+ latest_block = web3.eth.get_block('latest')
+ latest_block_number = latest_block['number']
+
+ max_wait_blocks = int(math.ceil(max_wait_seconds / 15))
+ block_num = max(1, latest_block_number - sample_size)
+
+ weighted_blocks = []
+ for block_number in range(block_num, latest_block_number + 1):
+ block = web3.eth.get_block(BlockNumber(block_number))
+ if weighted:
+ weight = (block_number - block_num + 1) / sample_size
+ else:
+ weight = 1
+
+ weighted_blocks.append((block, weight))
+
+ min_price = min(block['baseFeePerGas'] for block, _ in weighted_blocks)
+
+ miner_data = groupby(
+ operator.attrgetter('miner'),
+ MinerData(
+ block['miner'],
+ weight,
+ min_price,
+ percentile(block['baseFeePerGas'], 10)
+ ) for block, weight in weighted_blocks
+ )
+
+ miner_data = [
+ MinerData(
+ miner=miner,
+ num_blocks=sum(m.num_blocks for m in data),
+ min_gas_price=min(m.min_gas_price for m in data),
+ low_percentile_gas_price=min(m.low_percentile_gas_price for m in data),
+ )
+ for miner, data
+ in miner_data.items()
+ ]
+
+ raw_probabilities = _compute_probabilities(
+ miner_data,
+ wait_blocks=max_wait_blocks,
+ sample_size=sample_size,
+ )
+
+ probabilities = tuple(sorted(
+ raw_probabilities,
+ key=operator.attrgetter('gas_price'),
+ reverse=True,
+ ))
+
+ gas_price = _compute_gas_price(probabilities, probability / 100)
+
+ return gas_price
+
+ return time_based_gas_price_strategy
fast_gas_price_strategy = construct_time_based_gas_price_strategy(
diff --git a/web3/geth.py b/web3/geth.py
index 308930d9..08248b7a 100644
--- a/web3/geth.py
+++ b/web3/geth.py
@@ -138,6 +138,19 @@ class AsyncGethTxPool(Module):
https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool
"""
is_async = True
+
+ @property
+ async def content(self) -> TxPoolContent:
+ return await self._content()
+
+ @property
+ async def inspect(self) -> TxPoolInspect:
+ return await self._inspect()
+
+ @property
+ async def status(self) -> TxPoolStatus:
+ return await self._status()
+
_content: Method[Callable[[], Awaitable[TxPoolContent]]] = Method(RPC.
txpool_content, is_property=True)
_inspect: Method[Callable[[], Awaitable[TxPoolInspect]]] = Method(RPC.
@@ -151,6 +164,36 @@ class AsyncGethAdmin(Module):
https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-admin
"""
is_async = True
+
+ async def add_peer(self, enode: EnodeURI) -> bool:
+ return await self._add_peer(enode)
+
+ @property
+ async def datadir(self) -> str:
+ return await self._datadir()
+
+ @property
+ async def node_info(self) -> NodeInfo:
+ return await self._node_info()
+
+ @property
+ async def peers(self) -> List[Peer]:
+ return await self._peers()
+
+ async def start_http(self, host: str = 'localhost', port: int = 8545,
+ cors: str = '', apis: str = 'eth,net,web3') -> bool:
+ return await self._start_http(host, port, cors, apis)
+
+ async def stop_http(self) -> bool:
+ return await self._stop_http()
+
+ async def start_ws(self, host: str = 'localhost', port: int = 8546,
+ cors: str = '', apis: str = 'eth,net,web3') -> bool:
+ return await self._start_ws(host, port, cors, apis)
+
+ async def stop_ws(self) -> bool:
+ return await self._stop_ws()
+
_add_peer: Method[Callable[[EnodeURI], Awaitable[bool]]] = Method(RPC.
admin_addPeer, mungers=[default_root_munger])
_datadir: Method[Callable[[], Awaitable[str]]] = Method(RPC.
@@ -174,6 +217,39 @@ class AsyncGethPersonal(Module):
https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal
"""
is_async = True
+
+ async def ec_recover(self, message: str, signature: HexStr) -> ChecksumAddress:
+ return await self._ec_recover(message, signature)
+
+ async def import_raw_key(self, private_key: str, passphrase: str) -> ChecksumAddress:
+ return await self._import_raw_key(private_key, passphrase)
+
+ @property
+ async def list_accounts(self) -> List[ChecksumAddress]:
+ return await self._list_accounts()
+
+ @property
+ async def list_wallets(self) -> List[GethWallet]:
+ return await self._list_wallets()
+
+ async def send_transaction(self, transaction: TxParams, passphrase: str) -> HexBytes:
+ return await self._send_transaction(transaction, passphrase)
+
+ async def sign(self, message: str, account: ChecksumAddress, password: Optional[str] = None) -> HexStr:
+ return await self._sign(message, account, password)
+
+ async def sign_typed_data(self, jsonMessage: Dict[str, Any], account: ChecksumAddress, password: str) -> HexStr:
+ return await self._sign_typed_data(jsonMessage, account, password)
+
+ async def new_account(self, password: str) -> ChecksumAddress:
+ return await self._new_account(password)
+
+ async def lock_account(self, account: ChecksumAddress) -> bool:
+ return await self._lock_account(account)
+
+ async def unlock_account(self, account: ChecksumAddress, passphrase: str, duration: Optional[int] = None) -> bool:
+ return await self._unlock_account(account, passphrase, duration)
+
_ec_recover: Method[Callable[[str, HexStr], Awaitable[ChecksumAddress]]
] = Method(RPC.personal_ecRecover, mungers=[default_root_munger])
_import_raw_key: Method[Callable[[str, str], Awaitable[ChecksumAddress]]
diff --git a/web3/main.py b/web3/main.py
index f7375874..6a790045 100644
--- a/web3/main.py
+++ b/web3/main.py
@@ -59,14 +59,32 @@ class BaseWeb3:
Takes list of abi_types as inputs -- `[uint24, int8[], bool]`
and list of corresponding values -- `[20, [-1, 5, 0], True]`
"""
- pass
+ if len(abi_types) != len(values):
+ raise ValueError("Length mismatch between abi_types and values")
+ hex_data = add_0x_prefix(''.join(
+ remove_0x_prefix(hex_encode_abi_type(abi_type, value))
+ for abi_type, value in zip(abi_types, values)
+ ))
+ return eth_utils_keccak(hexstr=hex_data)
def attach_modules(self, modules: Optional[Dict[str, Union[Type[Module],
Sequence[Any]]]]) ->None:
"""
Attach modules to the `Web3` instance.
"""
- pass
+ if modules is not None:
+ for module_name, module_info in modules.items():
+ if isinstance(module_info, Sequence):
+ if len(module_info) == 2:
+ module_class, module_args = module_info
+ _attach_modules(self, ((module_name, module_class(*module_args)),))
+ elif len(module_info) == 1:
+ module_class = module_info[0]
+ _attach_modules(self, ((module_name, module_class(self)),))
+ else:
+ raise ValueError("Module sequence must have 1 or 2 elements")
+ else:
+ _attach_modules(self, ((module_name, module_info(self)),))
class Web3(BaseWeb3):
@@ -118,7 +136,7 @@ class AsyncWeb3(BaseWeb3):
Establish a persistent connection via websockets to a websocket provider using
a ``PersistentConnectionProvider`` instance.
"""
- pass
+ return _PersistentConnectionWeb3(provider, middlewares, modules, external_modules, ens)
class _PersistentConnectionWeb3(AsyncWeb3):
diff --git a/web3/manager.py b/web3/manager.py
index b3a25415..af5f5f1e 100644
--- a/web3/manager.py
+++ b/web3/manager.py
@@ -52,7 +52,14 @@ class RequestManager:
Leaving w3 unspecified will prevent the middleware from resolving names.
Documentation should remain in sync with these defaults.
"""
- pass
+ return [
+ (gas_price_strategy_middleware, 'gas_price_strategy'),
+ (name_to_address_middleware(w3), 'name_to_address'),
+ (attrdict_middleware, 'attrdict'),
+ (validation_middleware, 'validation'),
+ (abi_middleware, 'abi'),
+ (buffered_gas_estimate_middleware, 'gas_estimate'),
+ ]
@staticmethod
def async_default_middlewares() ->List[Tuple[AsyncMiddleware, str]]:
@@ -60,7 +67,13 @@ class RequestManager:
List the default async middlewares for the request manager.
Documentation should remain in sync with these defaults.
"""
- pass
+ return [
+ (async_gas_price_strategy_middleware, 'gas_price_strategy'),
+ (async_name_to_address_middleware, 'name_to_address'),
+ (async_attrdict_middleware, 'attrdict'),
+ (async_validation_middleware, 'validation'),
+ (async_buffered_gas_estimate_middleware, 'gas_estimate'),
+ ]
def request_blocking(self, method: Union[RPCEndpoint, Callable[...,
RPCEndpoint]], params: Any, error_formatters: Optional[Callable[...,
@@ -69,7 +82,31 @@ class RequestManager:
"""
Make a synchronous request using the provider
"""
- pass
+ response = self._make_request(method, params)
+ return self._process_response(response, error_formatters, null_result_formatters)
+
+ def _make_request(self, method: Union[RPCEndpoint, Callable[..., RPCEndpoint]], params: Any) ->RPCResponse:
+ if callable(method):
+ method = method(params)
+ middleware = self.middleware_onion.wrap(self.provider.make_request)
+ return middleware(method, params)
+
+ def _process_response(self, response: RPCResponse, error_formatters: Optional[Callable[..., Any]],
+ null_result_formatters: Optional[Callable[..., Any]]) ->Any:
+ if "error" in response:
+ apply_error_formatters = error_formatters or (lambda x: x)
+ formatted_error = apply_error_formatters(response["error"])
+ raise ValueError(formatted_error)
+ elif "result" in response:
+ result = response["result"]
+ if result is None or result == "0x" or result == HexBytes("0x"):
+ if null_result_formatters:
+ return null_result_formatters(result)
+ else:
+ return None
+ return result
+ else:
+ raise BadResponseFormat("The response was in an unexpected format and unable to be parsed")
async def coro_request(self, method: Union[RPCEndpoint, Callable[...,
RPCEndpoint]], params: Any, error_formatters: Optional[Callable[...,
@@ -78,7 +115,14 @@ class RequestManager:
"""
Coroutine for making a request using the provider
"""
- pass
+ response = await self._make_async_request(method, params)
+ return self._process_response(response, error_formatters, null_result_formatters)
+
+ async def _make_async_request(self, method: Union[RPCEndpoint, Callable[..., RPCEndpoint]], params: Any) ->RPCResponse:
+ if callable(method):
+ method = method(params)
+ middleware = self.middleware_onion.wrap(self.provider.request)
+ return await middleware(method, params)
class _AsyncPersistentMessageStream:
diff --git a/web3/method.py b/web3/method.py
index 145212b3..28efed3e 100644
--- a/web3/method.py
+++ b/web3/method.py
@@ -78,10 +78,16 @@ class Method(Generic[TFunc]):
return obj.retrieve_caller_fn(self)
@property
- def method_selector_fn(self) ->Callable[..., Union[RPCEndpoint,
- Callable[..., RPCEndpoint]]]:
+ def method_selector_fn(self) -> Callable[..., Union[RPCEndpoint, Callable[..., RPCEndpoint]]]:
"""Gets the method selector from the config."""
- pass
+ if callable(self.json_rpc_method):
+ return self.json_rpc_method
+ elif isinstance(self.json_rpc_method, str):
+ return lambda *_: self.json_rpc_method
+ elif self.method_choice_depends_on_args:
+ return self.method_choice_depends_on_args
+ else:
+ raise ValueError("Unable to determine method selector")
class DeprecatedMethod:
diff --git a/web3/middleware/async_cache.py b/web3/middleware/async_cache.py
index f8eb024c..6f249759 100644
--- a/web3/middleware/async_cache.py
+++ b/web3/middleware/async_cache.py
@@ -25,4 +25,27 @@ async def async_construct_simple_cache_middleware(cache: SimpleCache=None,
``response`` and returns a boolean as to whether the response should be
cached.
"""
- pass
+ if cache is None:
+ cache = SimpleCache(256)
+
+ async def async_middleware(make_request: Callable[[RPCEndpoint, Any], Any], w3: "AsyncWeb3") -> AsyncMiddlewareCoroutine:
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in rpc_whitelist:
+ cache_key = generate_cache_key((method, params))
+ cached_response = cache.get(cache_key)
+ if cached_response is not None:
+ return cached_response
+
+ response = await make_request(method, params)
+
+ if should_cache_fn(method, params, response):
+ async with async_lock():
+ cache.cache(cache_key, response)
+
+ return response
+ else:
+ return await make_request(method, params)
+
+ return middleware
+
+ return async_middleware
diff --git a/web3/middleware/attrdict.py b/web3/middleware/attrdict.py
index 06cd32d9..56add109 100644
--- a/web3/middleware/attrdict.py
+++ b/web3/middleware/attrdict.py
@@ -15,7 +15,13 @@ def attrdict_middleware(make_request: Callable[[RPCEndpoint, Any], Any],
Note: Accessing `AttributeDict` properties via attribute
(e.g. my_attribute_dict.property1) will not preserve typing.
"""
- pass
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ response = make_request(method, params)
+ if 'result' in response and isinstance(response['result'], dict):
+ response = assoc(response, 'result', AttributeDict.recursive(response['result']))
+ return cast(RPCResponse, response)
+
+ return middleware
async def async_attrdict_middleware(make_request: Callable[[RPCEndpoint,
@@ -26,4 +32,10 @@ async def async_attrdict_middleware(make_request: Callable[[RPCEndpoint,
Note: Accessing `AttributeDict` properties via attribute
(e.g. my_attribute_dict.property1) will not preserve typing.
"""
- pass
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ response = await make_request(method, params)
+ if 'result' in response and isinstance(response['result'], dict):
+ response = assoc(response, 'result', AttributeDict.recursive(response['result']))
+ return cast(RPCResponse, response)
+
+ return middleware
diff --git a/web3/middleware/cache.py b/web3/middleware/cache.py
index 18c93307..571fc1ac 100644
--- a/web3/middleware/cache.py
+++ b/web3/middleware/cache.py
@@ -32,7 +32,26 @@ def construct_simple_cache_middleware(cache: SimpleCache=None,
``response`` and returns a boolean as to whether the response should be
cached.
"""
- pass
+ if cache is None:
+ cache = SimpleCache()
+ if rpc_whitelist is None:
+ rpc_whitelist = SIMPLE_CACHE_RPC_WHITELIST
+
+ def middleware(make_request: Callable[[RPCEndpoint, Any], RPCResponse], w3: "Web3") -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware_fn(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in rpc_whitelist:
+ cache_key = generate_cache_key((method, params))
+ if cache_key in cache:
+ return cache[cache_key]
+
+ response = make_request(method, params)
+ if should_cache_fn(method, params, response):
+ cache[cache_key] = response
+ return response
+ else:
+ return make_request(method, params)
+ return middleware_fn
+ return middleware
_simple_cache_middleware = construct_simple_cache_middleware()
@@ -57,7 +76,25 @@ def construct_time_based_cache_middleware(cache_class: Callable[..., Dict[
``response`` and returns a boolean as to whether the response should be
cached.
"""
- pass
+ cache = cache_class()
+
+ def middleware(make_request: Callable[[RPCEndpoint, Any], RPCResponse], w3: "Web3") -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware_fn(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in rpc_whitelist:
+ cache_key = generate_cache_key((method, params))
+ if cache_key in cache:
+ cached_response, timestamp = cache[cache_key]
+ if time.time() - timestamp <= cache_expire_seconds:
+ return cached_response
+
+ response = make_request(method, params)
+ if should_cache_fn(method, params, response):
+ cache[cache_key] = (response, time.time())
+ return response
+ else:
+ return make_request(method, params)
+ return middleware_fn
+ return middleware
_time_based_cache_middleware = construct_time_based_cache_middleware(
@@ -103,7 +140,52 @@ def construct_latest_block_based_cache_middleware(cache_class: Callable[...,
a new block when the last seen latest block is older than the average
block time.
"""
- pass
+ cache = cache_class()
+ block_info: BlockInfoCache = {}
+
+ def middleware(make_request: Callable[[RPCEndpoint, Any], RPCResponse], w3: "Web3") -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware_fn(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in rpc_whitelist:
+ if "latest_block" not in block_info:
+ _update_block_info_cache(make_request, block_info)
+ elif time.time() - block_info["avg_block_time_updated_at"] > block_info["avg_block_time"]:
+ _update_block_info_cache(make_request, block_info)
+
+ latest_block = block_info["latest_block"]
+ cache_key = generate_cache_key((method, params, latest_block["hash"]))
+
+ if cache_key in cache:
+ return cache[cache_key]
+
+ response = make_request(method, params)
+ if should_cache_fn(method, params, response):
+ cache[cache_key] = response
+ return response
+ else:
+ return make_request(method, params)
+ return middleware_fn
+
+ def _update_block_info_cache(make_request: Callable[[RPCEndpoint, Any], RPCResponse], block_info: BlockInfoCache) -> None:
+ latest_block = make_request("eth_getBlockByNumber", ["latest", False])
+ if "latest_block" in block_info:
+ prev_block_number = block_info["latest_block"]["number"]
+ blocks_diff = int(latest_block["number"], 16) - int(prev_block_number, 16)
+ if blocks_diff > 0:
+ block_time = (int(latest_block["timestamp"], 16) - int(block_info["latest_block"]["timestamp"], 16)) / blocks_diff
+ if AVG_BLOCK_TIME_KEY in block_info:
+ block_info[AVG_BLOCK_TIME_KEY] = (block_info[AVG_BLOCK_TIME_KEY] * block_info.get(AVG_BLOCK_SAMPLE_SIZE_KEY, 0) + block_time) / (block_info.get(AVG_BLOCK_SAMPLE_SIZE_KEY, 0) + 1)
+ block_info[AVG_BLOCK_SAMPLE_SIZE_KEY] = min(block_info.get(AVG_BLOCK_SAMPLE_SIZE_KEY, 0) + 1, average_block_time_sample_size)
+ else:
+ block_info[AVG_BLOCK_TIME_KEY] = block_time
+ block_info[AVG_BLOCK_SAMPLE_SIZE_KEY] = 1
+ else:
+ block_info[AVG_BLOCK_TIME_KEY] = default_average_block_time
+ block_info[AVG_BLOCK_SAMPLE_SIZE_KEY] = 1
+
+ block_info["latest_block"] = latest_block
+ block_info[AVG_BLOCK_TIME_UPDATED_AT_KEY] = time.time()
+
+ return middleware
_latest_block_based_cache_middleware = (
diff --git a/web3/middleware/exception_retry_request.py b/web3/middleware/exception_retry_request.py
index cbaf3bd0..57c37158 100644
--- a/web3/middleware/exception_retry_request.py
+++ b/web3/middleware/exception_retry_request.py
@@ -36,7 +36,22 @@ def exception_retry_middleware(make_request: Callable[[RPCEndpoint, Any],
Creates middleware that retries failed HTTP requests. Is a default
middleware for HTTPProvider.
"""
- pass
+ allow_list = allow_list or DEFAULT_ALLOWLIST
+
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method not in allow_list:
+ return make_request(method, params)
+
+ for attempt in range(retries):
+ try:
+ return make_request(method, params)
+ except errors as e:
+ if attempt == retries - 1:
+ raise
+ backoff = backoff_factor * (2 ** attempt)
+ time.sleep(backoff)
+
+ return middleware
async def async_exception_retry_middleware(make_request: Callable[[
@@ -47,4 +62,19 @@ async def async_exception_retry_middleware(make_request: Callable[[
Creates middleware that retries failed HTTP requests.
Is a default middleware for AsyncHTTPProvider.
"""
- pass
+ allow_list = allow_list or DEFAULT_ALLOWLIST
+
+ async def middleware(method: RPCEndpoint, params: Any) -> Any:
+ if method not in allow_list:
+ return await make_request(method, params)
+
+ for attempt in range(retries):
+ try:
+ return await make_request(method, params)
+ except errors as e:
+ if attempt == retries - 1:
+ raise
+ backoff = backoff_factor * (2 ** attempt)
+ await asyncio.sleep(backoff)
+
+ return middleware
diff --git a/web3/middleware/filter.py b/web3/middleware/filter.py
index c552b7b2..68cdf4a5 100644
--- a/web3/middleware/filter.py
+++ b/web3/middleware/filter.py
@@ -41,7 +41,11 @@ def segment_count(start: int, stop: int, step: int=5) ->Iterable[Tuple[int,
>>> next(segment_counter) # Remainder is also returned
(9, 10)
"""
- pass
+ current = start
+ while current < stop:
+ next_segment = min(current + step, stop)
+ yield (current, next_segment)
+ current = next_segment
def block_ranges(start_block: BlockNumber, last_block: Optional[BlockNumber
@@ -51,7 +55,12 @@ def block_ranges(start_block: BlockNumber, last_block: Optional[BlockNumber
Ranges do not overlap to facilitate use as ``toBlock``, ``fromBlock``
json-rpc arguments, which are both inclusive.
"""
- pass
+ if last_block is None:
+ yield (start_block, None)
+ return
+
+ for from_block, to_block in segment_count(start_block, last_block + 1, step):
+ yield (BlockNumber(from_block), BlockNumber(to_block - 1))
def iter_latest_block(w3: 'Web3', to_block: Optional[Union[BlockNumber,
@@ -72,7 +81,16 @@ def iter_latest_block(w3: 'Web3', to_block: Optional[Union[BlockNumber,
10
>>> next(new_blocks) # latest block > to block
"""
- pass
+ last_block = None
+ while True:
+ latest_block = w3.eth.block_number
+ if latest_block != last_block:
+ if to_block is not None and latest_block > to_block:
+ return
+ yield latest_block
+ last_block = latest_block
+ else:
+ yield None
def iter_latest_block_ranges(w3: 'Web3', from_block: BlockNumber, to_block:
@@ -92,7 +110,13 @@ def iter_latest_block_ranges(w3: 'Web3', from_block: BlockNumber, to_block:
>>> next(blocks_to_filter) # latest block number = 50
(46, 50)
"""
- pass
+ for latest_block in iter_latest_block(w3, to_block):
+ if latest_block is None:
+ continue
+ yield (from_block, latest_block)
+ from_block = latest_block + 1
+ if to_block is not None and latest_block >= to_block:
+ break
def get_logs_multipart(w3: 'Web3', start_block: BlockNumber, stop_block:
@@ -104,7 +128,14 @@ def get_logs_multipart(w3: 'Web3', start_block: BlockNumber, stop_block:
The getLog request is partitioned into multiple calls of the max number of blocks
``max_blocks``.
"""
- pass
+ for from_block, to_block in block_ranges(start_block, stop_block, max_blocks):
+ params = {
+ 'fromBlock': from_block,
+ 'toBlock': to_block,
+ 'address': address,
+ 'topics': topics
+ }
+ yield w3.eth.get_logs(params)
class RequestLogs:
@@ -159,7 +190,16 @@ async def async_iter_latest_block(w3: 'Web3', to_block: Optional[Union[
10
>>> next(new_blocks) # latest block > to block
"""
- pass
+ last_block = None
+ while True:
+ latest_block = await w3.eth.block_number
+ if latest_block != last_block:
+ if to_block is not None and latest_block > to_block:
+ return
+ yield latest_block
+ last_block = latest_block
+ else:
+ yield None
async def async_iter_latest_block_ranges(w3: 'Web3', from_block:
@@ -179,7 +219,13 @@ async def async_iter_latest_block_ranges(w3: 'Web3', from_block:
>>> next(blocks_to_filter) # latest block number = 50
(46, 50)
"""
- pass
+ async for latest_block in async_iter_latest_block(w3, to_block):
+ if latest_block is None:
+ continue
+ yield (from_block, latest_block)
+ from_block = latest_block + 1
+ if to_block is not None and latest_block >= to_block:
+ break
async def async_get_logs_multipart(w3: 'Web3', start_block: BlockNumber,
@@ -191,7 +237,14 @@ async def async_get_logs_multipart(w3: 'Web3', start_block: BlockNumber,
The getLog request is partitioned into multiple calls of the max number of blocks
``max_blocks``.
"""
- pass
+ for from_block, to_block in block_ranges(start_block, stop_block, max_blocks):
+ params = {
+ 'fromBlock': from_block,
+ 'toBlock': to_block,
+ 'address': address,
+ 'topics': topics
+ }
+ yield await w3.eth.get_logs(params)
class AsyncRequestLogs:
diff --git a/web3/middleware/fixture.py b/web3/middleware/fixture.py
index d67010d5..99e71f29 100644
--- a/web3/middleware/fixture.py
+++ b/web3/middleware/fixture.py
@@ -11,7 +11,15 @@ def construct_fixture_middleware(fixtures: Dict[RPCEndpoint, Any]
Constructs a middleware which returns a static response for any method
which is found in the provided fixtures.
"""
- pass
+ def fixture_middleware(
+ make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3"
+ ) -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in fixtures:
+ return {"result": fixtures[method]}
+ return make_request(method, params)
+ return middleware
+ return fixture_middleware
def construct_result_generator_middleware(result_generators: Dict[
@@ -22,7 +30,16 @@ def construct_result_generator_middleware(result_generators: Dict[
whatever response the generator function returns. Callbacks must be
functions with the signature `fn(method, params)`.
"""
- pass
+ def result_generator_middleware(
+ make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3"
+ ) -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in result_generators:
+ result = result_generators[method](method, params)
+ return {"result": result}
+ return make_request(method, params)
+ return middleware
+ return result_generator_middleware
def construct_error_generator_middleware(error_generators: Dict[RPCEndpoint,
@@ -33,7 +50,16 @@ def construct_error_generator_middleware(error_generators: Dict[RPCEndpoint,
whatever error message the generator function returns. Callbacks must be
functions with the signature `fn(method, params)`.
"""
- pass
+ def error_generator_middleware(
+ make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3"
+ ) -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in error_generators:
+ error = error_generators[method](method, params)
+ return {"error": error}
+ return make_request(method, params)
+ return middleware
+ return error_generator_middleware
async def async_construct_result_generator_middleware(result_generators:
@@ -42,7 +68,16 @@ async def async_construct_result_generator_middleware(result_generators:
Constructs a middleware which returns a static response for any method
which is found in the provided fixtures.
"""
- pass
+ async def async_result_generator_middleware(
+ make_request: Callable[[RPCEndpoint, Any], Any], w3: "AsyncWeb3"
+ ) -> AsyncMiddlewareCoroutine:
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in result_generators:
+ result = result_generators[method](method, params)
+ return {"result": result}
+ return await make_request(method, params)
+ return middleware
+ return async_result_generator_middleware
async def async_construct_error_generator_middleware(error_generators: Dict
@@ -53,4 +88,13 @@ async def async_construct_error_generator_middleware(error_generators: Dict
whatever error message the generator function returns. Callbacks must be
functions with the signature `fn(method, params)`.
"""
- pass
+ async def async_error_generator_middleware(
+ make_request: Callable[[RPCEndpoint, Any], Any], w3: "AsyncWeb3"
+ ) -> AsyncMiddlewareCoroutine:
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method in error_generators:
+ error = error_generators[method](method, params)
+ return {"error": error}
+ return await make_request(method, params)
+ return middleware
+ return async_error_generator_middleware
diff --git a/web3/middleware/gas_price_strategy.py b/web3/middleware/gas_price_strategy.py
index 33d6387b..715f2b62 100644
--- a/web3/middleware/gas_price_strategy.py
+++ b/web3/middleware/gas_price_strategy.py
@@ -18,7 +18,32 @@ def gas_price_strategy_middleware(make_request: Callable[[RPCEndpoint, Any],
- Validates transaction params against legacy and dynamic fee txn values.
"""
- pass
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method != 'eth_sendTransaction':
+ return make_request(method, params)
+
+ transaction = params[0]
+
+ # Validate transaction params
+ if any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) and any_in_dict(['gasPrice'], transaction):
+ raise TransactionTypeMismatch("You cannot use legacy and EIP-1559 transaction parameters at the same time")
+
+ if none_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) and 'gasPrice' not in transaction:
+ if w3.eth.gas_price_strategy:
+ gas_price_strategy = w3.eth.gas_price_strategy
+ gas_price = gas_price_strategy(w3, transaction)
+ transaction = assoc(transaction, 'gasPrice', gas_price)
+ else:
+ raise InvalidTransaction("Gas price strategy not set and gasPrice not provided")
+
+ # Convert values to hex if they are integers
+ for key in ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']:
+ if key in transaction:
+ transaction[key] = to_hex_if_integer(transaction[key])
+
+ return make_request(method, [transaction])
+
+ return middleware
async def async_gas_price_strategy_middleware(make_request: Callable[[
@@ -30,4 +55,29 @@ async def async_gas_price_strategy_middleware(make_request: Callable[[
- Validates transaction params against legacy and dynamic fee txn values.
"""
- pass
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method != 'eth_sendTransaction':
+ return await make_request(method, params)
+
+ transaction = params[0]
+
+ # Validate transaction params
+ if any_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) and any_in_dict(['gasPrice'], transaction):
+ raise TransactionTypeMismatch("You cannot use legacy and EIP-1559 transaction parameters at the same time")
+
+ if none_in_dict(DYNAMIC_FEE_TXN_PARAMS, transaction) and 'gasPrice' not in transaction:
+ if async_w3.eth.gas_price_strategy:
+ gas_price_strategy = async_w3.eth.gas_price_strategy
+ gas_price = await gas_price_strategy(async_w3, transaction)
+ transaction = assoc(transaction, 'gasPrice', gas_price)
+ else:
+ raise InvalidTransaction("Gas price strategy not set and gasPrice not provided")
+
+ # Convert values to hex if they are integers
+ for key in ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']:
+ if key in transaction:
+ transaction[key] = to_hex_if_integer(transaction[key])
+
+ return await make_request(method, [transaction])
+
+ return middleware
diff --git a/web3/middleware/signing.py b/web3/middleware/signing.py
index 6cd23d57..44ea7724 100644
--- a/web3/middleware/signing.py
+++ b/web3/middleware/signing.py
@@ -32,7 +32,10 @@ def format_transaction(transaction: TxParams) ->TxParams:
the underlying layers. Also has the effect of normalizing 'from' for
easier comparisons.
"""
- pass
+ formatted_transaction = apply_abi_formatters_to_dict(TRANSACTION_PARAMS_ABIS, transaction)
+ if 'from' in formatted_transaction:
+ formatted_transaction['from'] = to_checksum_address(formatted_transaction['from'])
+ return formatted_transaction
def construct_sign_and_send_raw_middleware(private_key_or_account: Union[
@@ -47,7 +50,32 @@ def construct_sign_and_send_raw_middleware(private_key_or_account: Union[
- An eth_keys.PrivateKey object
- A raw private key as a hex string or byte string
"""
- pass
+ if isinstance(private_key_or_account, (list, tuple, set)):
+ accounts = [to_account(pk) for pk in private_key_or_account]
+ else:
+ accounts = [to_account(private_key_or_account)]
+
+ def middleware(make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3") -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware_fn(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method != "eth_sendTransaction":
+ return make_request(method, params)
+
+ transaction = format_transaction(params[0])
+ if 'from' not in transaction:
+ return make_request(method, params)
+
+ from_address = transaction['from']
+ account = next((acct for acct in accounts if acct.address == from_address), None)
+ if account is None:
+ return make_request(method, params)
+
+ transaction = fill_transaction_defaults(w3, transaction)
+ transaction = fill_nonce(w3, transaction)
+ signed = account.sign_transaction(transaction)
+ return make_request("eth_sendRawTransaction", [signed.rawTransaction])
+
+ return middleware_fn
+ return middleware
async def async_construct_sign_and_send_raw_middleware(private_key_or_account:
@@ -62,4 +90,29 @@ async def async_construct_sign_and_send_raw_middleware(private_key_or_account:
- An eth_keys.PrivateKey object
- A raw private key as a hex string or byte string
"""
- pass
+ if isinstance(private_key_or_account, (list, tuple, set)):
+ accounts = [to_account(pk) for pk in private_key_or_account]
+ else:
+ accounts = [to_account(private_key_or_account)]
+
+ async def middleware(make_request: Callable[[RPCEndpoint, Any], Any], w3: "AsyncWeb3") -> AsyncMiddlewareCoroutine:
+ async def middleware_fn(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method != "eth_sendTransaction":
+ return await make_request(method, params)
+
+ transaction = format_transaction(params[0])
+ if 'from' not in transaction:
+ return await make_request(method, params)
+
+ from_address = transaction['from']
+ account = next((acct for acct in accounts if acct.address == from_address), None)
+ if account is None:
+ return await make_request(method, params)
+
+ transaction = await async_fill_transaction_defaults(w3, transaction)
+ transaction = await async_fill_nonce(w3, transaction)
+ signed = account.sign_transaction(transaction)
+ return await make_request("eth_sendRawTransaction", [signed.rawTransaction])
+
+ return middleware_fn
+ return middleware
diff --git a/web3/middleware/stalecheck.py b/web3/middleware/stalecheck.py
index 9fc519d1..ef9e3efb 100644
--- a/web3/middleware/stalecheck.py
+++ b/web3/middleware/stalecheck.py
@@ -21,7 +21,18 @@ def make_stalecheck_middleware(allowable_delay: int,
If the latest block in the chain is older than 5 minutes in this example, then the
middleware will raise a StaleBlockchain exception.
"""
- pass
+ def stalecheck_middleware(make_request: Callable[[RPCEndpoint, Any], Any], w3: 'Web3') -> Callable[[RPCEndpoint, Any], RPCResponse]:
+ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method not in skip_stalecheck_for_methods:
+ latest_block = w3.eth.get_block('latest')
+ if latest_block is None:
+ raise StaleBlockchain("Latest block is None")
+ last_block_time = latest_block['timestamp']
+ if time.time() - last_block_time > allowable_delay:
+ raise StaleBlockchain(f"The latest block is {time.time() - last_block_time} seconds old, which exceeds the allowable delay of {allowable_delay} seconds")
+ return make_request(method, params)
+ return middleware
+ return stalecheck_middleware
async def async_make_stalecheck_middleware(allowable_delay: int,
@@ -38,4 +49,15 @@ async def async_make_stalecheck_middleware(allowable_delay: int,
If the latest block in the chain is older than 5 minutes in this example, then the
middleware will raise a StaleBlockchain exception.
"""
- pass
+ async def stalecheck_middleware(make_request: Callable[[RPCEndpoint, Any], Any], w3: 'AsyncWeb3') -> AsyncMiddlewareCoroutine:
+ async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
+ if method not in skip_stalecheck_for_methods:
+ latest_block = await w3.eth.get_block('latest')
+ if latest_block is None:
+ raise StaleBlockchain("Latest block is None")
+ last_block_time = latest_block['timestamp']
+ if time.time() - last_block_time > allowable_delay:
+ raise StaleBlockchain(f"The latest block is {time.time() - last_block_time} seconds old, which exceeds the allowable delay of {allowable_delay} seconds")
+ return await make_request(method, params)
+ return middleware
+ return stalecheck_middleware
diff --git a/web3/pm.py b/web3/pm.py
index 1a35c7d5..051ad89b 100644
--- a/web3/pm.py
+++ b/web3/pm.py
@@ -202,7 +202,8 @@ class PM(Module):
* Parameters:
* ``manifest``: A dict representing a valid manifest
"""
- pass
+ validate_manifest_against_schema(manifest)
+ return Package(manifest)
def get_package_from_uri(self, manifest_uri: URI) ->Package:
"""
@@ -214,7 +215,11 @@ class PM(Module):
* Parameters:
* ``uri``: Must be a valid content-addressed URI
"""
- pass
+ if not is_supported_content_addressed_uri(manifest_uri):
+ raise ValueError("URI must be a valid content-addressed URI")
+
+ manifest = json.loads(resolve_uri_contents(manifest_uri))
+ return self.get_package_from_manifest(manifest)
def get_local_package(self, package_name: str, ethpm_dir: Path=None
) ->Package:
@@ -226,7 +231,19 @@ class PM(Module):
* ``package_name``: Must be the name of a package installed locally.
* ``ethpm_dir``: Path pointing to the target ethpm directory (optional).
"""
- pass
+ if ethpm_dir is None:
+ ethpm_dir = Path.home() / '.ethpm'
+
+ package_dir = ethpm_dir / package_name
+ manifest_path = package_dir / 'manifest.json'
+
+ if not manifest_path.exists():
+ raise FileNotFoundError(f"Manifest not found for package: {package_name}")
+
+ with open(manifest_path) as f:
+ manifest = json.load(f)
+
+ return self.get_package_from_manifest(manifest)
def set_registry(self, address: Union[Address, ChecksumAddress, ENS]
) ->None:
@@ -242,7 +259,15 @@ class PM(Module):
* Parameters:
* ``address``: Address of on-chain Registry.
"""
- pass
+ if isinstance(address, str) and is_ens_name(address):
+ if not hasattr(self.w3, 'ens') or not isinstance(self.w3.ens, ENS):
+ raise ValueError("ENS is not set up on current web3 instance")
+ address = self.w3.ens.address(address)
+
+ if not is_checksum_address(address):
+ address = to_checksum_address(address)
+
+ self.registry = SimpleRegistry(address, self.w3)
def deploy_and_set_registry(self) ->ChecksumAddress:
"""
@@ -256,7 +281,9 @@ class PM(Module):
w3.ens.setup_address(ens_name, w3.pm.registry.address)
"""
- pass
+ deployed_registry = SimpleRegistry.deploy_new_instance(self.w3)
+ self.registry = deployed_registry
+ return deployed_registry.address
def release_package(self, package_name: str, version: str, manifest_uri:
URI) ->bytes:
@@ -274,7 +301,16 @@ class PM(Module):
supported.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ validate_package_name(package_name)
+ validate_package_version(version)
+
+ if not is_supported_content_addressed_uri(manifest_uri):
+ raise ValueError("manifest_uri must be a valid content-addressed URI")
+
+ return self.registry._release(package_name, version, manifest_uri)
@to_tuple
def get_all_package_names(self) ->Iterable[str]:
@@ -282,27 +318,43 @@ class PM(Module):
Returns a tuple containing all the package names
available on the current registry.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ for package_id in self.registry._get_all_package_ids():
+ yield self.registry._get_package_name(package_id)
def get_package_count(self) ->int:
"""
Returns the number of packages available on the current registry.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ return self.registry._num_package_ids()
def get_release_count(self, package_name: str) ->int:
"""
Returns the number of releases of the given package name
available on the current registry.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ validate_package_name(package_name)
+ return self.registry._num_release_ids(package_name)
def get_release_id(self, package_name: str, version: str) ->bytes:
"""
Returns the 32 byte identifier of a release for the given package
name and version, if they are available on the current registry.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ validate_package_name(package_name)
+ validate_package_version(version)
+ return self.registry._get_release_id(package_name, version)
@to_tuple
def get_all_package_releases(self, package_name: str) ->Iterable[Tuple[
@@ -311,7 +363,13 @@ class PM(Module):
Returns a tuple of release data (version, manifest_ur) for every release of the
given package name available on the current registry.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ validate_package_name(package_name)
+ for release_id in self.registry._get_all_release_ids(package_name):
+ release_data = self.registry._get_release_data(release_id)
+ yield (release_data.version, release_data.manifest_uri)
def get_release_id_data(self, release_id: bytes) ->ReleaseData:
"""
@@ -321,7 +379,10 @@ class PM(Module):
* Parameters:
* ``release_id``: 32 byte release identifier
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ return self.registry._get_release_data(release_id)
def get_release_data(self, package_name: str, version: str) ->ReleaseData:
"""
@@ -332,7 +393,13 @@ class PM(Module):
* ``name``: Must be a valid package name.
* ``version``: Must be a valid package version.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ validate_package_name(package_name)
+ validate_package_version(version)
+ release_id = self.get_release_id(package_name, version)
+ return self.get_release_id_data(release_id)
def get_package(self, package_name: str, version: str) ->Package:
"""
@@ -344,4 +411,9 @@ class PM(Module):
* ``name``: Must be a valid package name.
* ``version``: Must be a valid package version.
"""
- pass
+ if not self.registry:
+ raise ValueError("Registry not set. Use set_registry() first.")
+
+ release_data = self.get_release_data(package_name, version)
+ manifest_uri = release_data.manifest_uri
+ return self.get_package_from_uri(manifest_uri)
diff --git a/web3/providers/base.py b/web3/providers/base.py
index 83113b8d..ca7b373f 100644
--- a/web3/providers/base.py
+++ b/web3/providers/base.py
@@ -26,7 +26,15 @@ class BaseProvider:
@returns a function that calls all the middleware and
eventually self.make_request()
"""
- pass
+ middlewares = tuple(outer_middlewares) + self._middlewares
+ cache_key = self._middlewares
+ if cache_key != self._request_func_cache[0]:
+ self._request_func_cache = (cache_key, combine_middlewares(
+ middlewares=middlewares,
+ w3=w3,
+ provider_request_fn=self.make_request,
+ ))
+ return self._request_func_cache[1]
class JSONBaseProvider(BaseProvider):
diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py
index 96005e39..51beb236 100644
--- a/web3/providers/eth_tester/defaults.py
+++ b/web3/providers/eth_tester/defaults.py
@@ -21,12 +21,13 @@ null_if_filter_not_found = null_if_excepts(FilterNotFound)
null_if_indexerror = null_if_excepts(IndexError)
-def _generate_random_private_key() ->HexStr:
+def _generate_random_private_key() -> HexStr:
"""
WARNING: This is not a secure way to generate private keys and should only
be used for testing purposes.
"""
- pass
+ private_key = '0x' + ''.join([random.choice('0123456789abcdef') for _ in range(64)])
+ return HexStr(private_key)
API_ENDPOINTS = {'web3': {'clientVersion': client_version, 'sha3': compose(
diff --git a/web3/providers/ipc.py b/web3/providers/ipc.py
index 116d38f3..8e630b3a 100644
--- a/web3/providers/ipc.py
+++ b/web3/providers/ipc.py
@@ -35,6 +35,11 @@ class PersistantSocket:
pass
self.sock = None
+ def _open(self) ->socket.socket:
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(self.ipc_path)
+ return sock
+
class IPCProvider(JSONBaseProvider):
logger = logging.getLogger('web3.providers.IPCProvider')
@@ -55,3 +60,44 @@ class IPCProvider(JSONBaseProvider):
def __str__(self) ->str:
return f'<{self.__class__.__name__} {self.ipc_path}>'
+
+ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
+ request = self.encode_rpc_request(method, params)
+ with self._lock, self._socket as sock:
+ try:
+ sock.sendall(request)
+ response_raw = b""
+ with Timeout(self.timeout) as timeout:
+ while True:
+ try:
+ response_raw += sock.recv(4096)
+ except socket.timeout:
+ timeout.sleep(0)
+ continue
+ if response_raw and response_raw[-1] == ord("\n"):
+ break
+ except (ConnectionError, OSError) as e:
+ raise ConnectionError(f"Could not connect to IPC socket at path: {self.ipc_path}") from e
+ return self.decode_rpc_response(response_raw)
+
+ def isConnected(self) -> bool:
+ try:
+ with self._lock, self._socket as sock:
+ sock.sendall(self.encode_rpc_request("web3_clientVersion", []))
+ sock.recv(1)
+ return True
+ except (ConnectionError, OSError):
+ return False
+
+def get_default_ipc_path() -> str:
+ if sys.platform.startswith('darwin'):
+ return os.path.expanduser("~/Library/Ethereum/geth.ipc")
+ elif sys.platform.startswith('linux'):
+ return os.path.expanduser("~/.ethereum/geth.ipc")
+ elif sys.platform.startswith('win'):
+ return r"\\\\.\\pipe\\geth.ipc"
+ else:
+ raise ValueError(
+ "Unsupported platform '{0}'. Unable to determine "
+ "the default ipc path.".format(sys.platform)
+ )
diff --git a/web3/providers/persistent.py b/web3/providers/persistent.py
index 81b2301b..48525e76 100644
--- a/web3/providers/persistent.py
+++ b/web3/providers/persistent.py
@@ -41,7 +41,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
exception and keep the listener task alive. Override this method to fine-tune
error logging behavior for the implementation class.
"""
- pass
+ self.logger.error(f"Listener task exception: {e}", exc_info=True)
def _handle_listener_task_exceptions(self) ->None:
"""
@@ -49,4 +49,109 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
messages in the main loop. If the message listener task has completed and an
exception was recorded, raise the exception in the main loop.
"""
- pass
+ if self._message_listener_task and self._message_listener_task.done():
+ exception = self._message_listener_task.exception()
+ if exception:
+ if self.silence_listener_task_exceptions:
+ self._error_log_listener_task_exception(exception)
+ # Restart the listener task
+ self._start_listener_task()
+ else:
+ raise exception
+
+ def _start_listener_task(self) ->None:
+ """
+ Start the message listener task.
+ """
+ if self._message_listener_task is None or self._message_listener_task.done():
+ self._message_listener_task = asyncio.create_task(self._listen_for_messages())
+
+ async def _listen_for_messages(self) ->None:
+ """
+ Listen for messages from the WebSocket connection.
+ """
+ while True:
+ try:
+ if self._ws:
+ message = await self._ws.recv()
+ await self._request_processor.process_response(message)
+ except (ConnectionClosed, ConnectionClosedOK, WebSocketException) as e:
+ self.logger.warning(f"WebSocket connection closed: {e}")
+ await self._reconnect()
+ except Exception as e:
+ self.logger.error(f"Error in message listener: {e}", exc_info=True)
+ if not self.silence_listener_task_exceptions:
+ raise
+
+ async def _reconnect(self) ->None:
+ """
+ Attempt to reconnect to the WebSocket.
+ """
+ for attempt in range(self._max_connection_retries):
+ try:
+ await self.connect()
+ self.logger.info("Reconnected to WebSocket")
+ return
+ except Exception as e:
+ self.logger.warning(f"Reconnection attempt {attempt + 1} failed: {e}")
+ await asyncio.sleep(2 ** attempt) # Exponential backoff
+ raise ProviderConnectionError("Failed to reconnect after multiple attempts")
+
+ async def connect(self) ->None:
+ """
+ Establish a WebSocket connection.
+ """
+ if not self.endpoint_uri:
+ raise ValueError("No endpoint URI specified")
+
+ try:
+ self._ws = await asyncio.wait_for(
+ WebSocketClientProtocol.__aenter__(self.endpoint_uri),
+ timeout=self.request_timeout
+ )
+ self._start_listener_task()
+ self._listen_event.set()
+ except (asyncio.TimeoutError, WebSocketException) as e:
+ raise ProviderConnectionError(f"Failed to connect to {self.endpoint_uri}: {e}")
+
+ async def disconnect(self) ->None:
+ """
+ Close the WebSocket connection.
+ """
+ if self._ws:
+ await self._ws.close()
+ self._ws = None
+ if self._message_listener_task:
+ self._message_listener_task.cancel()
+ try:
+ await self._message_listener_task
+ except asyncio.CancelledError:
+ pass
+ self._listen_event.clear()
+
+ async def is_connected(self) ->bool:
+ """
+ Check if the WebSocket connection is established and open.
+ """
+ return self._ws is not None and self._ws.open
+
+ async def make_request(self, method: RPCEndpoint, params: Any) ->RPCResponse:
+ """
+ Make an RPC request over the WebSocket connection.
+ """
+ if not await self.is_connected():
+ await self.connect()
+
+ request_id = generate_cache_key(method, params)
+ request = self.encode_rpc_request(method, params)
+
+ try:
+ response = await asyncio.wait_for(
+ self._request_processor.send_request(request_id, request),
+ timeout=self.request_timeout
+ )
+ return self.decode_rpc_response(response)
+ except asyncio.TimeoutError:
+ raise TimeExhausted(f"Request {request_id} timed out after {self.request_timeout} seconds")
+ except Exception as e:
+ raise ProviderConnectionError(f"Error making request: {e}")
diff --git a/web3/providers/websocket/request_processor.py b/web3/providers/websocket/request_processor.py
index 704c0352..00262254 100644
--- a/web3/providers/websocket/request_processor.py
+++ b/web3/providers/websocket/request_processor.py
@@ -49,10 +49,24 @@ class RequestProcessor:
request is made but inner requests, say to `eth_estimateGas` if the `gas` is
missing, are made before the original request is sent.
"""
- pass
+ if cache_key in self._request_information_cache:
+ old_request_info = self._request_information_cache[cache_key]
+ new_request_id = old_request_info.request_id + 1
+ new_cache_key = generate_cache_key(
+ old_request_info.method,
+ old_request_info.params,
+ new_request_id
+ )
+ self._request_information_cache[new_cache_key] = RequestInformation(
+ old_request_info.method,
+ old_request_info.params,
+ new_request_id
+ )
+ del self._request_information_cache[cache_key]
def clear_caches(self) ->None:
"""
Clear the request processor caches.
"""
- pass
+ self._request_response_cache.clear()
+ self._request_information_cache.clear()
diff --git a/web3/providers/websocket/websocket.py b/web3/providers/websocket/websocket.py
index 23c1a7e4..f6e88a36 100644
--- a/web3/providers/websocket/websocket.py
+++ b/web3/providers/websocket/websocket.py
@@ -65,3 +65,31 @@ class WebsocketProvider(JSONBaseProvider):
def __str__(self) ->str:
return f'WS connection {self.endpoint_uri}'
+
+ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
+ request_data = self.encode_rpc_request(method, params)
+ response = self._loop.run_until_complete(self._make_request(request_data))
+ return self.decode_rpc_response(response)
+
+ async def _make_request(self, request_data: str) -> str:
+ async with self.conn as ws:
+ await ws.send(request_data)
+ response = await asyncio.wait_for(ws.recv(), timeout=self.websocket_timeout)
+ return response
+
+ def is_connected(self) -> bool:
+ return self._loop.run_until_complete(self._is_connected())
+
+ async def _is_connected(self) -> bool:
+ try:
+ async with self.conn as ws:
+ return ws.open
+ except Exception:
+ return False
+
+ def disconnect(self) -> None:
+ self._loop.run_until_complete(self._disconnect())
+
+ async def _disconnect(self) -> None:
+ if self.conn.ws and self.conn.ws.open:
+ await self.conn.ws.close()
diff --git a/web3/testing.py b/web3/testing.py
index 40e4637c..5d933bc0 100644
--- a/web3/testing.py
+++ b/web3/testing.py
@@ -1,7 +1,137 @@
-from typing import Optional
+from typing import Optional, Any, Dict, List
from web3._utils.rpc_abi import RPC
from web3.module import Module
+from web3.types import Wei, BlockIdentifier
class Testing(Module):
- pass
+ def timeTravel(self, timestamp: int) -> bool:
+ """
+ Fast forward to a future timestamp.
+
+ :param timestamp: The timestamp to fast forward to.
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(RPC.testing_timeTravel, [timestamp])
+
+ def mine(self, num_blocks: int = 1) -> bool:
+ """
+ Mine a specified number of blocks.
+
+ :param num_blocks: Number of blocks to mine (default: 1).
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(RPC.evm_mine, [num_blocks])
+
+ def snapshot(self) -> str:
+ """
+ Take a snapshot of the current state of the blockchain.
+
+ :return: The ID of the snapshot.
+ """
+ return self.web3.manager.request_blocking(RPC.evm_snapshot, [])
+
+ def revert(self, snapshot_id: str) -> bool:
+ """
+ Revert the blockchain to a previous snapshot.
+
+ :param snapshot_id: The ID of the snapshot to revert to.
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(RPC.evm_revert, [snapshot_id])
+
+ def reset_to_genesis(self) -> bool:
+ """
+ Reset the blockchain to its genesis state.
+
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(RPC.evm_reset, [])
+
+ def set_balance(self, address: str, balance: Wei) -> bool:
+ """
+ Set the balance of an account.
+
+ :param address: The address of the account.
+ :param balance: The new balance in Wei.
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(
+ RPC.testing_setBalance,
+ [address, hex(balance)]
+ )
+
+ def set_code(self, address: str, code: str) -> bool:
+ """
+ Set the code of a contract.
+
+ :param address: The address of the contract.
+ :param code: The new code as a hexadecimal string.
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(
+ RPC.testing_setCode,
+ [address, code]
+ )
+
+ def set_storage_at(self, address: str, position: int, value: str) -> bool:
+ """
+ Set a storage slot of a contract.
+
+ :param address: The address of the contract.
+ :param position: The storage slot position.
+ :param value: The value to set at the storage slot.
+ :return: True if successful, False otherwise.
+ """
+ return self.web3.manager.request_blocking(
+ RPC.testing_setStorageAt,
+ [address, hex(position), value]
+ )
+
+ def get_block_by_number(self, block_identifier: BlockIdentifier) -> Dict[str, Any]:
+ """
+ Get block information by block number.
+
+ :param block_identifier: The block number or a predefined block identifier.
+ :return: A dictionary containing block information.
+ """
+ return self.web3.manager.request_blocking(
+ RPC.eth_getBlockByNumber,
+ [block_identifier, True]
+ )
+
+ def get_transaction_receipt(self, tx_hash: str) -> Dict[str, Any]:
+ """
+ Get the receipt of a transaction.
+
+ :param tx_hash: The transaction hash.
+ :return: A dictionary containing the transaction receipt.
+ """
+ return self.web3.manager.request_blocking(
+ RPC.eth_getTransactionReceipt,
+ [tx_hash]
+ )
+
+ def get_logs(self, from_block: BlockIdentifier, to_block: BlockIdentifier, address: Optional[str] = None, topics: Optional[List[str]] = None) -> List[Dict[str, Any]]:
+ """
+ Get logs matching the given parameters.
+
+ :param from_block: The starting block (inclusive).
+ :param to_block: The ending block (inclusive).
+ :param address: (Optional) The address to get logs from.
+ :param topics: (Optional) Array of 32 Bytes DATA topics.
+ :return: A list of log objects.
+ """
+ filter_params = {
+ "fromBlock": from_block,
+ "toBlock": to_block,
+ }
+ if address:
+ filter_params["address"] = address
+ if topics:
+ filter_params["topics"] = topics
+
+ return self.web3.manager.request_blocking(
+ RPC.eth_getLogs,
+ [filter_params]
+ )
diff --git a/web3/tools/pytest_ethereum/_utils.py b/web3/tools/pytest_ethereum/_utils.py
index 443aece3..b9ed57ee 100644
--- a/web3/tools/pytest_ethereum/_utils.py
+++ b/web3/tools/pytest_ethereum/_utils.py
@@ -15,7 +15,10 @@ def pluck_matching_uri(deployment_data: Dict[URI, Dict[str, str]], w3: Web3
Return any blockchain uri that matches w3-connected chain, if one
is present in the deployment data keys.
"""
- pass
+ for uri in deployment_data.keys():
+ if check_if_chain_matches_chain_uri(w3, uri):
+ return uri
+ return None
def contains_matching_uri(deployment_data: Dict[str, Dict[str, str]], w3: Web3
@@ -24,7 +27,7 @@ def contains_matching_uri(deployment_data: Dict[str, Dict[str, str]], w3: Web3
Returns true if any blockchain uri in deployment data matches
w3-connected chain.
"""
- pass
+ return any(check_if_chain_matches_chain_uri(w3, uri) for uri in deployment_data.keys())
def insert_deployment(package: Package, deployment_name: str,
@@ -35,11 +38,32 @@ def insert_deployment(package: Package, deployment_name: str,
with the new deployment data. If no match, it will simply add
the new chain uri and deployment data.
"""
- pass
+ old_deployments = package.manifest.get('deployments', {})
+
+ if latest_block_uri in old_deployments:
+ updated_deployments = assoc_in(
+ old_deployments,
+ [latest_block_uri, deployment_name],
+ deployment_data
+ )
+ else:
+ updated_deployments = assoc(
+ old_deployments,
+ latest_block_uri,
+ {deployment_name: deployment_data}
+ )
+
+ return assoc(package.manifest, 'deployments', updated_deployments)
def get_deployment_address(linked_type: str, package: Package) ->Address:
"""
Return the address of a linked_type found in a package's manifest deployments.
"""
- pass
+ deployments = package.manifest.get('deployments', {})
+
+ for deployment_data in deployments.values():
+ if linked_type in deployment_data:
+ return Address(deployment_data[linked_type]['address'])
+
+ raise LinkerError(f"Unable to find deployment address for {linked_type}")
diff --git a/web3/tools/pytest_ethereum/exceptions.py b/web3/tools/pytest_ethereum/exceptions.py
index 3125e3dd..8ddb8325 100644
--- a/web3/tools/pytest_ethereum/exceptions.py
+++ b/web3/tools/pytest_ethereum/exceptions.py
@@ -2,18 +2,22 @@ class PytestEthereumError(Exception):
"""
Base class for all Pytest-Ethereum errors.
"""
- pass
+ def __init__(self, message="An error occurred in Pytest-Ethereum"):
+ self.message = message
+ super().__init__(self.message)
class DeployerError(PytestEthereumError):
"""
Raised when the Deployer is unable to deploy a contract type.
"""
- pass
+ def __init__(self, message="Unable to deploy contract"):
+ super().__init__(message)
class LinkerError(PytestEthereumError):
"""
Raised when the Linker is unable to link two contract types.
"""
- pass
+ def __init__(self, message="Unable to link contract types"):
+ super().__init__(message)
diff --git a/web3/tools/pytest_ethereum/linker.py b/web3/tools/pytest_ethereum/linker.py
index 8a276c1e..b9874cd6 100644
--- a/web3/tools/pytest_ethereum/linker.py
+++ b/web3/tools/pytest_ethereum/linker.py
@@ -18,7 +18,42 @@ def deploy(contract_name: str, *args: Any, transaction: Dict[str, Any]=None
a deployment is found on the current w3 instance, it will return that deployment
rather than creating a new instance.
"""
- pass
+ def _deploy(package: Package) -> Package:
+ deployments = package.deployments
+ if contract_name in deployments:
+ return package
+
+ factory = package.get_contract_factory(contract_name)
+ if not factory:
+ raise LinkerError(f"Contract factory for {contract_name} not found in package.")
+
+ w3 = package.w3
+ if transaction is None:
+ deploy_transaction = {}
+ else:
+ deploy_transaction = transaction.copy()
+
+ if "from" not in deploy_transaction:
+ deploy_transaction["from"] = w3.eth.accounts[0]
+
+ tx_hash = factory.constructor(*args).transact(deploy_transaction)
+ tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
+ address = tx_receipt["contractAddress"]
+
+ deployment_data = create_deployment_data(
+ contract_name,
+ to_checksum_address(address),
+ tx_receipt,
+ )
+ latest_block_uri = create_latest_block_uri(w3, tx_receipt)
+ return insert_deployment(
+ package,
+ contract_name,
+ deployment_data,
+ latest_block_uri
+ )
+
+ return _deploy
@curry
@@ -27,7 +62,27 @@ def link(contract: ContractName, linked_type: str, package: Package) ->Package:
Return a new package, created with a new manifest after applying the linked type
reference to the contract factory.
"""
- pass
+ deployment_address = get_deployment_address(package, linked_type)
+ if not deployment_address:
+ raise LinkerError(f"No deployment found for contract {linked_type}")
+
+ factory = package.get_contract_factory(contract)
+ if not factory:
+ raise LinkerError(f"Contract factory for {contract} not found in package.")
+
+ linked_factory = factory.link_bytecode({linked_type: deployment_address})
+
+ # Update the contract factory in the package
+ updated_manifest = pipe(
+ package.manifest,
+ lambda manifest: assoc_in(
+ manifest,
+ ["contract_types", contract, "deployment_bytecode", "bytecode"],
+ to_hex(linked_factory.bytecode)
+ )
+ )
+
+ return Package(updated_manifest, package.w3)
@curry
@@ -36,4 +91,5 @@ def run_python(callback_fn: Callable[..., None], package: Package) ->Package:
Return the unmodified package, after performing any user-defined
callback function on the contracts in the package.
"""
- pass
+ callback_fn(package)
+ return package
diff --git a/web3/tools/pytest_ethereum/plugins.py b/web3/tools/pytest_ethereum/plugins.py
index 92f8e27d..bdad0ee5 100644
--- a/web3/tools/pytest_ethereum/plugins.py
+++ b/web3/tools/pytest_ethereum/plugins.py
@@ -8,9 +8,14 @@ from web3.tools.pytest_ethereum.deployer import Deployer
@pytest.fixture
-def deployer(w3: Web3) ->Callable[[Path], Deployer]:
+def deployer(w3: Web3) -> Callable[[Path], Deployer]:
"""
Returns a `Deployer` instance composed from a `Package` instance
generated from the manifest located at the provided `path` folder.
"""
- pass
+ def _deployer(path: Path) -> Deployer:
+ manifest = json.loads(path.read_text())
+ package = Package(manifest, w3)
+ return Deployer(package)
+
+ return _deployer
diff --git a/web3/utils/address.py b/web3/utils/address.py
index dcb5fa09..56ed2dee 100644
--- a/web3/utils/address.py
+++ b/web3/utils/address.py
@@ -8,7 +8,10 @@ def get_create_address(sender: HexAddress, nonce: Nonce) ->ChecksumAddress:
"""
Determine the resulting `CREATE` opcode contract address for a sender and a nonce.
"""
- pass
+ sender_bytes = to_bytes(hexstr=sender)
+ nonce_bytes = rlp.encode(nonce)
+ raw_address = keccak(rlp.encode([sender_bytes, nonce_bytes]))
+ return to_checksum_address(raw_address[12:])
def get_create2_address(sender: HexAddress, salt: HexStr, init_code: HexStr
@@ -17,4 +20,10 @@ def get_create2_address(sender: HexAddress, salt: HexStr, init_code: HexStr
Determine the resulting `CREATE2` opcode contract address for a sender, salt and
bytecode.
"""
- pass
+ prefix = b'\xff'
+ sender_bytes = to_bytes(hexstr=sender)
+ salt_bytes = to_bytes(hexstr=salt)
+ init_code_bytes = to_bytes(hexstr=init_code)
+ init_code_hash = keccak(init_code_bytes)
+ raw_address = keccak(prefix + sender_bytes + salt_bytes + init_code_hash)
+ return to_checksum_address(raw_address[12:])