diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 263090571c..d16d9aa892 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -41,6 +41,7 @@ Test fixtures for use by clients are available for each release on the [Github r - 🐞 Fix erroneous fork message in pytest session header with development forks ([#806](https://github.com/ethereum/execution-spec-tests/pull/806)). - 🐞 Fix `Conditional` code generator in EOF mode ([#821](https://github.com/ethereum/execution-spec-tests/pull/821)) - 🔀 `ethereum_test_rpc` library has been created with what was previously `ethereum_test_tools.rpc` ([#822](https://github.com/ethereum/execution-spec-tests/pull/822)) +- ✨ Add `Wei` type to `ethereum_test_base_types` which allows parsing wei amounts from strings like "1 ether", "1000 wei", "10**2 gwei", etc ([#825](https://github.com/ethereum/execution-spec-tests/pull/825)) ### 🔧 EVM Tools diff --git a/src/ethereum_test_base_types/__init__.py b/src/ethereum_test_base_types/__init__.py index 3b4ee79fe1..8dc5e80395 100644 --- a/src/ethereum_test_base_types/__init__.py +++ b/src/ethereum_test_base_types/__init__.py @@ -15,6 +15,7 @@ HexNumber, Number, NumberBoundTypeVar, + Wei, ZeroPaddedHexNumber, ) from .composite_types import Account, Alloc, Storage, StorageRootType @@ -60,6 +61,7 @@ "TestAddress2", "TestPrivateKey", "TestPrivateKey2", + "Wei", "ZeroPaddedHexNumber", "to_bytes", "to_hex", diff --git a/src/ethereum_test_base_types/base_types.py b/src/ethereum_test_base_types/base_types.py index 4e911a4241..01f80df796 100644 --- a/src/ethereum_test_base_types/base_types.py +++ b/src/ethereum_test_base_types/base_types.py @@ -75,6 +75,56 @@ def or_none(cls: Type[N], input: N | NumberConvertible | None) -> N | None: return cls(input) +class Wei(Number): + """ + Class that helps represent wei that can be parsed from strings + """ + + def __new__(cls, input: NumberConvertible | N): + """ + Creates a new Number object. + """ + if isinstance(input, str): + words = input.split() + multiplier = 1 + assert len(words) <= 2 + value_str = words[0] + if len(words) > 1: + unit = words[1].lower() + multiplier = cls._get_multiplier(unit) + value: float + if "**" in value_str: + base, exp = value_str.split("**") + value = float(base) ** int(exp) + else: + value = float(value_str) + return super(Number, cls).__new__(cls, value * multiplier) + return super(Number, cls).__new__(cls, to_number(input)) + + @staticmethod + def _get_multiplier(unit: str) -> int: + """ + Returns the multiplier for the given unit of wei, handling synonyms. + """ + match unit: + case "wei": + return 1 + case "kwei" | "babbage" | "femtoether": + return 10**3 + case "mwei" | "lovelace" | "picoether": + return 10**6 + case "gwei" | "shannon" | "nanoether" | "nano": + return 10**9 + case "szabo" | "microether" | "micro": + return 10**12 + case "finney" | "milliether" | "milli": + return 10**15 + case "ether": + return 10**18 + case _: + raise ValueError(f"Invalid unit {unit}") + + class HexNumber(Number): """ Class that helps represent an hexadecimal numbers in tests. diff --git a/src/ethereum_test_base_types/tests/test_base_types.py b/src/ethereum_test_base_types/tests/test_base_types.py index 0ef5f3b8ce..4bc8413e16 100644 --- a/src/ethereum_test_base_types/tests/test_base_types.py +++ b/src/ethereum_test_base_types/tests/test_base_types.py @@ -6,7 +6,7 @@ import pytest -from ..base_types import Address, Hash +from ..base_types import Address, Hash, Wei @pytest.mark.parametrize( @@ -52,3 +52,42 @@ def test_comparisons(a: Any, b: Any, equal: bool): else: assert a != b assert not a == b + + +@pytest.mark.parametrize( + "s, expected", + [ + ("0", 0), + ("10**18", 10**18), + ("1e18", 10**18), + ("1 ether", 10**18), + ("2 ether", 2 * 10**18), + ("2.1 ether", 2.1 * 10**18), + ("2.1 Ether", 2.1 * 10**18), + ("2.1 ETHER", 2.1 * 10**18), + ("1 wei", 1), + ("10**9 wei", 10**9), + ("1 gwei", 10**9), + ("1 szabo", 10**12), + ("1 finney", 10**15), + ("1 kwei", 10**3), + ("1 mwei", 10**6), + ("1 babbage", 10**3), + ("1 femtoether", 10**3), + ("1 Lovelace", 10**6), + ("1 Picoether", 10**6), + ("1 gwei", 10**9), + ("1 shannon", 10**9), + ("1 nanoether", 10**9), + ("1 nano", 10**9), + ("1 microether", 10**12), + ("1 micro", 10**12), + ("1 milliether", 10**15), + ("1 milli", 10**15), + ], +) +def test_wei_parsing(s: str, expected: int): + """ + Test the parsing of wei values. + """ + assert Wei(s) == expected