diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index b743d505ed..5e82fe3a45 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -2926,10 +2926,7 @@ class WalletRpcApi: async def get_coin_records(self, request: Dict[str, Any]) -> EndpointResult: parsed_request = GetCoinRecords.from_json_dict(request) - if ( - parsed_request.limit != uint32.MAXIMUM_EXCLUSIVE - 1 - and parsed_request.limit > self.max_get_coin_records_limit - ): + if parsed_request.limit != uint32.MAXIMUM and parsed_request.limit > self.max_get_coin_records_limit: raise ValueError(f"limit of {self.max_get_coin_records_limit} exceeded: {parsed_request.limit}") for filter_name, filter in { diff --git a/chia/util/ints.py b/chia/util/ints.py index cbc1cd24ca..629fe09d54 100644 --- a/chia/util/ints.py +++ b/chia/util/ints.py @@ -1,51 +1,62 @@ from __future__ import annotations +from typing import ClassVar + from chia.util.struct_stream import StructStream, parse_metadata_from_name @parse_metadata_from_name class int8(StructStream): - pass + MINIMUM: ClassVar[int8] + MAXIMUM: ClassVar[int8] @parse_metadata_from_name class uint8(StructStream): - pass + MINIMUM: ClassVar[uint8] + MAXIMUM: ClassVar[uint8] @parse_metadata_from_name class int16(StructStream): - pass + MINIMUM: ClassVar[int16] + MAXIMUM: ClassVar[int16] @parse_metadata_from_name class uint16(StructStream): - pass + MINIMUM: ClassVar[uint16] + MAXIMUM: ClassVar[uint16] @parse_metadata_from_name class int32(StructStream): - pass + MINIMUM: ClassVar[int32] + MAXIMUM: ClassVar[int32] @parse_metadata_from_name class uint32(StructStream): - pass + MINIMUM: ClassVar[uint32] + MAXIMUM: ClassVar[uint32] @parse_metadata_from_name class int64(StructStream): - pass + MINIMUM: ClassVar[int64] + MAXIMUM: ClassVar[int64] @parse_metadata_from_name class uint64(StructStream): - pass + MINIMUM: ClassVar[uint64] + MAXIMUM: ClassVar[uint64] @parse_metadata_from_name class uint128(StructStream): - pass + MINIMUM: ClassVar[uint128] + MAXIMUM: ClassVar[uint128] class int512(StructStream): @@ -59,5 +70,9 @@ class int512(StructStream): # note that the boundaries for int512 is not what you might expect. We # encode these with one extra byte, but only allow a range of # [-INT512_MAX, INT512_MAX] - MAXIMUM_EXCLUSIVE = 2**BITS - MINIMUM = -(2**BITS) + 1 + MINIMUM: ClassVar[int512] = -(2**BITS) + 1 + MAXIMUM: ClassVar[int512] = (2**BITS) - 1 + + +int512.MINIMUM = int512(int512.MINIMUM) +int512.MAXIMUM = int512(int512.MAXIMUM) diff --git a/chia/util/misc.py b/chia/util/misc.py index 0d192e9cdd..27db586c15 100644 --- a/chia/util/misc.py +++ b/chia/util/misc.py @@ -124,14 +124,14 @@ else: @dataclasses.dataclass(frozen=True) class UInt32Range(Streamable): start: uint32 = uint32(0) - stop: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1) + stop: uint32 = uint32.MAXIMUM @streamable @dataclasses.dataclass(frozen=True) class UInt64Range(Streamable): start: uint64 = uint64(0) - stop: uint64 = uint64(uint64.MAXIMUM_EXCLUSIVE - 1) + stop: uint64 = uint64.MAXIMUM @dataclass(frozen=True) diff --git a/chia/util/struct_stream.py b/chia/util/struct_stream.py index dacda0a0ea..3654872233 100644 --- a/chia/util/struct_stream.py +++ b/chia/util/struct_stream.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import BinaryIO, SupportsInt, Type, TypeVar, Union +from typing import BinaryIO, ClassVar, SupportsInt, Type, TypeVar, Union from typing_extensions import Protocol, SupportsIndex @@ -37,24 +37,28 @@ def parse_metadata_from_name(cls: Type[_T_StructStream]) -> Type[_T_StructStream raise ValueError(f"cls.BITS must be a multiple of 8: {cls.BITS}") if cls.SIGNED: - cls.MAXIMUM_EXCLUSIVE = 2 ** (cls.BITS - 1) cls.MINIMUM = -(2 ** (cls.BITS - 1)) + cls.MAXIMUM = (2 ** (cls.BITS - 1)) - 1 else: - cls.MAXIMUM_EXCLUSIVE = 2**cls.BITS cls.MINIMUM = 0 + cls.MAXIMUM = (2**cls.BITS) - 1 + + cls.MINIMUM = cls(cls.MINIMUM) + cls.MAXIMUM = cls(cls.MAXIMUM) return cls class StructStream(int): - SIZE = 0 - BITS = 0 - SIGNED = False - MAXIMUM_EXCLUSIVE = 0 - MINIMUM = 0 + SIZE: ClassVar[int] + BITS: ClassVar[int] + SIGNED: ClassVar[bool] + MAXIMUM: ClassVar[int] + MINIMUM: ClassVar[int] """ - Create a class that can parse and stream itself based on a struct.pack template string. + Create a class that can parse and stream itself based on a struct.pack template string. This is only meant to be + a base class for further derivation and it's not recommended to instantiate it directly. """ # This is just a partial exposure of the underlying int constructor. Liskov... @@ -65,7 +69,7 @@ class StructStream(int): # additional special action to take here beyond verifying that the newly # created instance satisfies the bounds limitations of the particular subclass. super().__init__() - if not (self.MINIMUM <= self < self.MAXIMUM_EXCLUSIVE): + if not (self.MINIMUM <= self <= self.MAXIMUM): raise ValueError(f"Value {self} does not fit into {type(self).__name__}") @classmethod diff --git a/chia/wallet/wallet_coin_store.py b/chia/wallet/wallet_coin_store.py index 4a6efdf34c..72d29dd5f9 100644 --- a/chia/wallet/wallet_coin_store.py +++ b/chia/wallet/wallet_coin_store.py @@ -29,7 +29,7 @@ class CoinRecordOrder(IntEnum): @dataclass(frozen=True) class GetCoinRecords(Streamable): offset: uint32 = uint32(0) - limit: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1) + limit: uint32 = uint32.MAXIMUM wallet_id: Optional[uint32] = None wallet_type: Optional[uint8] = None # WalletType coin_type: Optional[uint8] = None # CoinType @@ -188,7 +188,7 @@ class WalletCoinStore: self, *, offset: uint32 = uint32(0), - limit: uint32 = uint32(uint32.MAXIMUM_EXCLUSIVE - 1), + limit: uint32 = uint32.MAXIMUM, wallet_id: Optional[uint32] = None, wallet_type: Optional[WalletType] = None, coin_type: Optional[CoinType] = None, @@ -237,7 +237,7 @@ class WalletCoinStore: where_sql = "WHERE " + " AND ".join(conditions) if len(conditions) > 0 else "" order_sql = f"ORDER BY {order.name} {'DESC' if reverse else 'ASC'}, rowid" - limit_sql = f"LIMIT {offset}, {limit}" if offset > 0 or limit < uint32.MAXIMUM_EXCLUSIVE - 1 else "" + limit_sql = f"LIMIT {offset}, {limit}" if offset > 0 or limit < uint32.MAXIMUM else "" query_sql = f"{where_sql} {order_sql} {limit_sql}" async with self.db_wrapper.reader_no_transaction() as conn: diff --git a/tests/util/test_struct_stream.py b/tests/util/test_struct_stream.py index 002636c099..a723600192 100644 --- a/tests/util/test_struct_stream.py +++ b/tests/util/test_struct_stream.py @@ -41,7 +41,7 @@ class Good: size: int bits: int signed: bool - maximum_exclusive: int + maximum: int minimum: int @classmethod @@ -50,7 +50,7 @@ class Good: name: str, size: int, signed: bool, - maximum_exclusive: int, + maximum: int, minimum: int, ) -> Good: raw_class: Type[StructStream] = type(name, (StructStream,), {}) @@ -61,30 +61,30 @@ class Good: size=size, bits=size * 8, signed=signed, - maximum_exclusive=maximum_exclusive, + maximum=maximum, minimum=minimum, ) good_classes = [ - Good.create(name="uint8", size=1, signed=False, maximum_exclusive=0xFF + 1, minimum=0), - Good.create(name="int8", size=1, signed=True, maximum_exclusive=0x80, minimum=-0x80), - Good.create(name="uint16", size=2, signed=False, maximum_exclusive=0xFFFF + 1, minimum=0), - Good.create(name="int16", size=2, signed=True, maximum_exclusive=0x8000, minimum=-0x8000), - Good.create(name="uint24", size=3, signed=False, maximum_exclusive=0xFFFFFF + 1, minimum=0), - Good.create(name="int24", size=3, signed=True, maximum_exclusive=0x800000, minimum=-0x800000), + Good.create(name="uint8", size=1, signed=False, maximum=0xFF, minimum=0), + Good.create(name="int8", size=1, signed=True, maximum=0x7F, minimum=-0x80), + Good.create(name="uint16", size=2, signed=False, maximum=0xFFFF, minimum=0), + Good.create(name="int16", size=2, signed=True, maximum=0x7FFF, minimum=-0x8000), + Good.create(name="uint24", size=3, signed=False, maximum=0xFFFFFF, minimum=0), + Good.create(name="int24", size=3, signed=True, maximum=0x7FFFFF, minimum=-0x800000), Good.create( name="uint128", size=16, signed=False, - maximum_exclusive=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + 1, + maximum=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, minimum=0, ), Good.create( name="int128", size=16, signed=True, - maximum_exclusive=0x80000000000000000000000000000000, + maximum=0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, minimum=-0x80000000000000000000000000000000, ), ] @@ -145,6 +145,9 @@ class TestStructStream: with pytest.raises(struct.error): struct.pack(struct_format, upper_boundary + 1) + assert type(cls.MINIMUM) == cls + assert type(cls.MAXIMUM) == cls + def test_int512(self) -> None: # int512 is special. it uses 65 bytes to allow positive and negative # "uint512" @@ -245,7 +248,7 @@ class TestStructStream: assert uint32(b"273") == 273 def test_struct_stream_cannot_be_instantiated_directly(self) -> None: - with pytest.raises(ValueError, match="does not fit"): + with pytest.raises(AttributeError, match="object has no attribute"): StructStream(0) @pytest.mark.parametrize( @@ -281,7 +284,7 @@ class TestStructStream: assert good.cls.SIGNED == good.signed def test_parse_metadata_from_name_correct_maximum(self, good: Good) -> None: - assert good.cls.MAXIMUM_EXCLUSIVE == good.maximum_exclusive + assert good.cls.MAXIMUM == good.maximum def test_parse_metadata_from_name_correct_minimum(self, good: Good) -> None: assert good.cls.MINIMUM == good.minimum