From 189e284ae76818a2217456bb5aca0a3d2e9e7733 Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 15 Dec 2023 12:17:32 +0900 Subject: [PATCH 1/8] [WIP] ClaimItems action using GQL --- common/_graphql.py | 22 ++++++++++++++++++++++ worker/worker/handler.py | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/common/_graphql.py b/common/_graphql.py index 1ed228a2..af6ddb78 100644 --- a/common/_graphql.py +++ b/common/_graphql.py @@ -80,6 +80,28 @@ def _unload_from_garage(self, pubkey: bytes, nonce: int, **kwargs) -> bytes: result = self.execute(query) return bytes.fromhex(result["actionTxQuery"]["unloadFromMyGarages"]) + def _claim_items(self, pubkey: bytes, nonce: int, **kwargs) -> bytes: + ts = kwargs.get("timestamp", (datetime.datetime.utcnow()+datetime.timedelta(days=1)).isoformat()) + claim_data = kwargs.get("claim_data") + memo = kwargs.get("memo") + + query = dsl_gql( + DSLQuery( + self.ds.StandaloneQuery.actionTxQuery.args( + publicKey=pubkey.hex(), + nonce=nonce, + timestamp=ts, + ).select( + self.ds.ActionTxQuery.claimItems.args( + claimData=claim_data, + memo=memo, + ) + ) + ) + ) + result = self.execute(query) + return bytes.fromhex(result["actionTxQuery"]["unloadFromMyGarages"]) + def _transfer_asset(self, pubkey: bytes, nonce: int, **kwargs) -> bytes: ts = kwargs.get("timestamp", (datetime.datetime.utcnow()+datetime.timedelta(days=1)).isoformat()) sender = kwargs.get("sender") diff --git a/worker/worker/handler.py b/worker/worker/handler.py index f93d46a5..c9c51348 100644 --- a/worker/worker/handler.py +++ b/worker/worker/handler.py @@ -129,6 +129,10 @@ def process(sess: Session, message: SQSMessageRecord, nonce: int = None) -> Tupl public_key=account.pubkey.hex(), address=account.address, nonce=nonce, plain_value=unload_from_garage, timestamp=datetime.datetime.utcnow() + datetime.timedelta(days=1) ) + + unsigned_tx = gql.create_action("claim_items", pubkey=account.pubkey.hex(), nonce=nonce, + claim_data={}, memo=memo) + signature = account.sign_tx(unsigned_tx) signed_tx = append_signature_to_unsigned_tx(unsigned_tx, signature) return gql.stage(signed_tx), nonce, signed_tx From 421c9e03d07caf5315d5e861272e79eee361cb75 Mon Sep 17 00:00:00 2001 From: hyeon Date: Wed, 10 Jan 2024 17:15:46 +0900 Subject: [PATCH 2/8] Separate Currency model --- common/lib9c/currency.py | 31 ---------- common/lib9c/models/currency.py | 87 +++++++++++++++++++++++++++++ tests/lib9c/models/test_currency.py | 58 +++++++++++++++++++ tests/lib9c/test_currency.py | 30 ---------- 4 files changed, 145 insertions(+), 61 deletions(-) delete mode 100644 common/lib9c/currency.py create mode 100644 common/lib9c/models/currency.py create mode 100644 tests/lib9c/models/test_currency.py delete mode 100644 tests/lib9c/test_currency.py diff --git a/common/lib9c/currency.py b/common/lib9c/currency.py deleted file mode 100644 index 2409476a..00000000 --- a/common/lib9c/currency.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Dict, Any, Union - -import bencodex - -class Currency(): - - @staticmethod - def to_currency(ticker: str) -> Dict[str, Union[str, int, None]]: - if ticker.lower() == "crystal": - return { - "decimalPlaces": b'\x12', - "minters": None, - "ticker": "CRYSTAL", - } - elif ticker.lower() == "garage": - return { - "decimalPlaces": b'\x12', - "minters": None, - "ticker": "GARAGE", - "totalSupplyTrackable": True, - } - else: - return { - "decimalPlaces": b'\x00', - "minters": None, - "ticker": ticker.upper(), - } - - @staticmethod - def serialize(currency: Dict[str, Union[str, int, None]]) -> bytes: - return bencodex.dumps(currency) diff --git a/common/lib9c/models/currency.py b/common/lib9c/models/currency.py new file mode 100644 index 00000000..66dea7a7 --- /dev/null +++ b/common/lib9c/models/currency.py @@ -0,0 +1,87 @@ +from __future__ import annotations +from typing import Dict, Union, List, Optional, Any + +import bencodex + +from common.lib9c.models.address import Address + + +class Currency: + """ + # Currency + --- + Lib9c Currency model which has ticker, minters, decimal_places, total_supply_trackable. + `minters` will be automatically sanitized to `None` if empty list provided. + """ + + def __init__(self, ticker: str, decimal_places: int, minters: Optional[List[str]] = None, + total_supply_trackable: bool = False): + self.ticker = ticker + self.minters = [Address(x) for x in minters] if minters else None + self.decimal_places = decimal_places + self.total_supply_trackable = total_supply_trackable + + def __eq__(self, other:Currency): + return ( + self.ticker == other.ticker and + self.minters == other.minters and + self.decimal_places == other.decimal_places and + self.total_supply_trackable == other.total_supply_trackable + ) + + @classmethod + def NCG(cls): + return cls( + ticker="NCG", + minters=["47d082a115c63e7b58b1532d20e631538eafadde"], + decimal_places=2 + ) + + @classmethod + def CRYSTAL(cls): + return cls( + ticker="CRYSTAL", + minters=None, + decimal_places=18 + ) + + @property + def plain_value(self) -> Dict[str, Any]: + value = { + "ticker": self.ticker, + "decimalPlaces": chr(self.decimal_places).encode(), + "minters": [x.short_format for x in self.minters] if self.minters else None + } + if self.total_supply_trackable: + value["totalSupplyTrackable"] = True + return value + + @property + def serialized_plain_value(self) -> bytes: + return bencodex.dumps(self.plain_value) + + @staticmethod + def to_currency_plain_value(ticker: str) -> Dict[str, Union[str, int, None]]: + if ticker.lower() == "crystal": + return { + "decimalPlaces": b'\x12', + "minters": None, + "ticker": "CRYSTAL", + } + elif ticker.lower() == "garage": + return { + "decimalPlaces": b'\x12', + "minters": None, + "ticker": "GARAGE", + "totalSupplyTrackable": True, + } + else: + return { + "decimalPlaces": b'\x00', + "minters": None, + "ticker": ticker.upper(), + } + + @staticmethod + def serialize(currency: Dict[str, Union[str, int, None]]) -> bytes: + return bencodex.dumps(currency) diff --git a/tests/lib9c/models/test_currency.py b/tests/lib9c/models/test_currency.py new file mode 100644 index 00000000..d2630d62 --- /dev/null +++ b/tests/lib9c/models/test_currency.py @@ -0,0 +1,58 @@ +import pytest + +from common.lib9c.models.address import Address +from common.lib9c.models.currency import Currency + +TEST_DATASET = [ + ("NCG", 2, ["47d082a115c63e7b58b1532d20e631538eafadde"], False, + b'du13:decimalPlaces1:\x02u7:minterslu40:47d082a115c63e7b58b1532d20e631538eafaddeeu6:tickeru3:NCGe'), + ("CRYSTAL", 18, None, False, b'du13:decimalPlaces1:\x12u7:mintersnu6:tickeru7:CRYSTALe'), + ("GARAGE", 18, None, True, b'du13:decimalPlaces1:\x12u7:mintersnu6:tickeru6:GARAGEu20:totalSupplyTrackablete'), + ("OTHER", 0, None, False, b'du13:decimalPlaces1:\x00u7:mintersnu6:tickeru5:OTHERe'), + ("OTHER", 0, [], False, b'du13:decimalPlaces1:\x00u7:mintersnu6:tickeru5:OTHERe'), + ("OTHER", 0, ["0x896cB1A849d8818BF8e1fcf4166DafD67E27Dce0"], False, + b'du13:decimalPlaces1:\x00u7:minterslu40:896cb1a849d8818bf8e1fcf4166dafd67e27dce0eu6:tickeru5:OTHERe'), + ("OTHER", 0, ["0x896cB1A849d8818BF8e1fcf4166DafD67E27Dce0", "0x3C32731b77C5D99D186572E5ce5d6AA93A8853dC"], False, + b'du13:decimalPlaces1:\x00u7:minterslu40:896cb1a849d8818bf8e1fcf4166dafd67e27dce0u40:3c32731b77c5d99d186572e5ce5d6aa93a8853dceu6:tickeru5:OTHERe'), +] + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_currency(test_data): + ticker, decimal_places, minters, total_supply_trackable, _ = test_data + currency = Currency(ticker, decimal_places, minters, total_supply_trackable) + assert currency.ticker == ticker + assert currency.decimal_places == decimal_places + assert currency.minters == ([Address(x) for x in minters] if minters else None) + if total_supply_trackable: + assert currency.total_supply_trackable is total_supply_trackable + + +def test_well_known_currency(): + test_ncg = Currency.NCG() + expected_ncg = Currency("NCG", 2, ["47d082a115c63e7b58b1532d20e631538eafadde"], False) + assert test_ncg == expected_ncg + + test_crystal = Currency.CRYSTAL() + expected_crystal = Currency("CRYSTAL", 18, None, False) + assert test_crystal == expected_crystal + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_plain_value(test_data): + ticker, decimal_places, minters, total_supply_trackable, _ = test_data + currency = Currency(ticker, decimal_places, minters, total_supply_trackable) + plain_value = currency.plain_value + assert plain_value["ticker"] == ticker + assert plain_value["decimalPlaces"] == chr(decimal_places).encode() + assert plain_value["minters"] == ( + [x[2:].lower() if x.startswith("0x") else x.lower() for x in minters] if minters else None) + if total_supply_trackable: + assert plain_value["totalSupplyTrackable"] == total_supply_trackable + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_serialized_plain_value(test_data): + ticker, decimal_places, minters, total_supply_trackable, serialized = test_data + currency = Currency(ticker, decimal_places, minters, total_supply_trackable) + assert currency.serialized_plain_value == serialized diff --git a/tests/lib9c/test_currency.py b/tests/lib9c/test_currency.py deleted file mode 100644 index 72a192e3..00000000 --- a/tests/lib9c/test_currency.py +++ /dev/null @@ -1,30 +0,0 @@ -import bencodex -from common.lib9c.currency import Currency - - -def test_crystal(): - crystal = Currency.to_currency("crystal") - assert crystal["decimalPlaces"] == b'\x12' - assert crystal["minters"] == None - assert crystal["ticker"] == "CRYSTAL" - -def test_garage(): - garage = Currency.to_currency("garage") - assert garage["decimalPlaces"] == b'\x12' - assert garage["minters"] == None - assert garage["ticker"] == "GARAGE" - assert garage["totalSupplyTrackable"] == True - -def test_other(): - other = Currency.to_currency("other") - assert other["decimalPlaces"] == b'\x00' - assert other["minters"] == None - assert other["ticker"] == "OTHER" - -def test_serialize(): - crystal = Currency.to_currency("crystal") - assert Currency.serialize(crystal) == bencodex.dumps({'decimalPlaces': b'\x12', 'minters': None, 'ticker': 'CRYSTAL'}) - garage = Currency.to_currency("garage") - assert Currency.serialize(garage) == bencodex.dumps({'decimalPlaces': b'\x12', 'minters': None, 'ticker': 'GARAGE', 'totalSupplyTrackable': True}) - other = Currency.to_currency("other") - assert Currency.serialize(other) == bencodex.dumps({'decimalPlaces': b'\x00', 'minters': None, 'ticker': 'OTHER'}) From 8971b1bc342d1b04cddec7fa8685cf221c4e8a01 Mon Sep 17 00:00:00 2001 From: hyeon Date: Thu, 11 Jan 2024 15:18:45 +0900 Subject: [PATCH 3/8] Separate Address model --- common/lib9c/models/address.py | 24 ++++++++++++++++++++++++ tests/lib9c/models/test_address.py | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 common/lib9c/models/address.py create mode 100644 tests/lib9c/models/test_address.py diff --git a/common/lib9c/models/address.py b/common/lib9c/models/address.py new file mode 100644 index 00000000..c8086946 --- /dev/null +++ b/common/lib9c/models/address.py @@ -0,0 +1,24 @@ +from __future__ import annotations + + +class Address: + def __init__(self, addr: str): + if addr.startswith("0x"): + if len(addr) != 42: + raise ValueError("Address with 0x prefix must have exact 42 chars.") + self.raw = bytes.fromhex(addr[2:]) + else: + if len(addr) != 40: + raise ValueError("Address without 0x prefix must have exact 40 chars.") + self.raw = bytes.fromhex(addr) + + @property + def long_format(self): + return f"0x{self.raw.hex()}" + + @property + def short_format(self): + return self.raw.hex() + + def __eq__(self, other: Address): + return self.raw == other.raw diff --git a/tests/lib9c/models/test_address.py b/tests/lib9c/models/test_address.py new file mode 100644 index 00000000..9d19b306 --- /dev/null +++ b/tests/lib9c/models/test_address.py @@ -0,0 +1,20 @@ +import pytest + +from common.lib9c.models.address import Address + + +@pytest.mark.parametrize("addr", + ["0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D", + "0xb3cbca0e64aeb4b5b861047fe1db5a1bec1c241f", + "a5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D", + "b3cbca0e64aeb4b5b861047fe1db5a1bec1c241f", + ]) +def test_address(addr): + address = Address(addr) + assert len(address.raw) == 20 + if addr.startswith("0x"): + assert address.raw == bytes.fromhex(addr[2:]) + assert address.long_format == addr.lower() + else: + assert address.raw == bytes.fromhex(addr) + assert address.short_format == addr.lower() From 2a08c987ea5a79d2568572fbbdde1b75cb0e1a01 Mon Sep 17 00:00:00 2001 From: hyeon Date: Thu, 11 Jan 2024 17:23:03 +0900 Subject: [PATCH 4/8] Separate FungibleAssetValue model --- common/lib9c/fungible_asset.py | 16 ---- common/lib9c/models/fungible_asset_value.py | 44 ++++++++++ .../lib9c/models/test_fungible_asset_value.py | 88 +++++++++++++++++++ tests/lib9c/test_fungible_asset.py | 13 --- 4 files changed, 132 insertions(+), 29 deletions(-) delete mode 100644 common/lib9c/fungible_asset.py create mode 100644 common/lib9c/models/fungible_asset_value.py create mode 100644 tests/lib9c/models/test_fungible_asset_value.py delete mode 100644 tests/lib9c/test_fungible_asset.py diff --git a/common/lib9c/fungible_asset.py b/common/lib9c/fungible_asset.py deleted file mode 100644 index ab56c6f9..00000000 --- a/common/lib9c/fungible_asset.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Dict, Union, List - -import bencodex - -from common.lib9c.currency import Currency - - -class FungibleAsset(): - - @staticmethod - def to_fungible_asset(ticker: str, amount: int, decimalPlaces: int) -> List[Union[Dict[str, Union[str, int, None]], int]]: - return [Currency.to_currency(ticker), amount * max(1, 10 ** decimalPlaces)] - - @staticmethod - def serialize(fungible_asset: List[Union[Dict[str, Union[str, int, None]], int]]) -> bytes: - return bencodex.dumps([Currency.serialize(fungible_asset[0]), fungible_asset[1]]) diff --git a/common/lib9c/models/fungible_asset_value.py b/common/lib9c/models/fungible_asset_value.py new file mode 100644 index 00000000..42c2c96c --- /dev/null +++ b/common/lib9c/models/fungible_asset_value.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import Dict, Union, List, Optional, Any + +import bencodex + +from common.lib9c.models.currency import Currency + + +class FungibleAssetValue: + def __init__(self, currency: Currency, amount: float): + self.currency = currency + self.amount = amount + + def __eq__(self, other: FungibleAssetValue): + return self.currency == other.currency and self.amount == other.amount + + @classmethod + def from_raw_data( + cls, + ticker: str, decimal_places: int, minters: Optional[List[str]] = None, total_supply_trackable: bool = False, + amount: float = 0 + ): + return cls( + Currency(ticker, decimal_places, minters, total_supply_trackable), + amount=amount + ) + + @property + def plain_value(self) -> List[Dict[str, Any] | float]: + return [self.currency.plain_value, self.amount * max(1, 10 ** self.currency.decimal_places)] + + @property + def serialized_plain_value(self) -> bytes: + return bencodex.dumps(self.plain_value) + + @staticmethod + def to_fungible_asset(ticker: str, amount: int, decimal_places: int) \ + -> List[Union[Dict[str, Union[str, int, None]], int]]: + return [Currency.to_currency_plain_value(ticker), amount * max(1, 10 ** decimal_places)] + + @staticmethod + def serialize(fungible_asset: List[Union[Dict[str, Union[str, int, None]], int]]) -> bytes: + return bencodex.dumps([Currency.serialize(fungible_asset[0]), fungible_asset[1]]) diff --git a/tests/lib9c/models/test_fungible_asset_value.py b/tests/lib9c/models/test_fungible_asset_value.py new file mode 100644 index 00000000..d32ad0ab --- /dev/null +++ b/tests/lib9c/models/test_fungible_asset_value.py @@ -0,0 +1,88 @@ +import pytest + +from common.lib9c.models.currency import Currency +from common.lib9c.models.fungible_asset_value import FungibleAssetValue + +TEST_DATASET = [ + ("NCG", 2, ["47d082a115c63e7b58b1532d20e631538eafadde"], False, 0, + b'ldu13:decimalPlaces1:\x02u7:minterslu40:47d082a115c63e7b58b1532d20e631538eafaddeeu6:tickeru3:NCGei0ee'), + ("CRYSTAL", 18, None, False, 0, b'ldu13:decimalPlaces1:\x12u7:mintersnu6:tickeru7:CRYSTALei0ee'), + ("GARAGE", 18, None, True, 0, + b'ldu13:decimalPlaces1:\x12u7:mintersnu6:tickeru6:GARAGEu20:totalSupplyTrackabletei0ee'), + ("OTHER", 0, None, False, 0, b'ldu13:decimalPlaces1:\x00u7:mintersnu6:tickeru5:OTHERei0ee'), + ( + "OTHER", 0, ["0x896cB1A849d8818BF8e1fcf4166DafD67E27Dce0", "0x3C32731b77C5D99D186572E5ce5d6AA93A8853dC"], False, + 0, + b'ldu13:decimalPlaces1:\x00u7:minterslu40:896cb1a849d8818bf8e1fcf4166dafd67e27dce0u40:3c32731b77c5d99d186572e5ce5d6aa93a8853dceu6:tickeru5:OTHERei0ee' + ), + ("NCG", 2, ["47d082a115c63e7b58b1532d20e631538eafadde"], False, 1, + b'ldu13:decimalPlaces1:\x02u7:minterslu40:47d082a115c63e7b58b1532d20e631538eafaddeeu6:tickeru3:NCGei100ee'), + ("CRYSTAL", 18, None, False, 1, b'ldu13:decimalPlaces1:\x12u7:mintersnu6:tickeru7:CRYSTALei1000000000000000000ee'), + ("GARAGE", 18, None, True, 1, + b'ldu13:decimalPlaces1:\x12u7:mintersnu6:tickeru6:GARAGEu20:totalSupplyTrackabletei1000000000000000000ee'), + ("OTHER", 0, None, False, 1, b'ldu13:decimalPlaces1:\x00u7:mintersnu6:tickeru5:OTHERei1ee'), + ( + "OTHER", 0, ["0x896cB1A849d8818BF8e1fcf4166DafD67E27Dce0", "0x3C32731b77C5D99D186572E5ce5d6AA93A8853dC"], False, + 1, + b'ldu13:decimalPlaces1:\x00u7:minterslu40:896cb1a849d8818bf8e1fcf4166dafd67e27dce0u40:3c32731b77c5d99d186572e5ce5d6aa93a8853dceu6:tickeru5:OTHERei1ee' + ), +] + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_fav(test_data): + ticker, decimal_places, minters, total_supply_trackable, amount, _ = test_data + currency = Currency(ticker, decimal_places, minters, total_supply_trackable) + fav = FungibleAssetValue(currency, amount) + assert fav.currency == currency + assert fav.amount == amount + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_fav_from_data(test_data): + ticker, decimal_places, minters, total_supply_trackable, amount, _ = test_data + fav = FungibleAssetValue.from_raw_data(ticker, decimal_places, minters, total_supply_trackable, amount) + expected_currency = Currency(ticker, decimal_places, minters, total_supply_trackable) + assert fav.currency == expected_currency + assert fav.amount == amount + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_plain_value(test_data): + ticker, decimal_places, minters, total_supply_trackable, amount, _ = test_data + fav = FungibleAssetValue.from_raw_data(ticker, decimal_places, minters, total_supply_trackable, amount) + plain_value = fav.plain_value + assert plain_value[0]["ticker"] == ticker + assert plain_value[0]["decimalPlaces"] == chr(decimal_places).encode() + assert plain_value[0]["minters"] == ([x[2:].lower() if x.startswith("0x") else x.lower() for x in minters] + if minters else None) + if total_supply_trackable: + assert plain_value[0]["totalSupplyTrackable"] is True + else: + assert "totalSupplyTrackable" not in plain_value[0] + assert plain_value[1] == amount * max(1, 10 ** decimal_places) + + +@pytest.mark.parametrize("test_data", TEST_DATASET) +def test_serialized_plain_value(test_data): + ticker, decimal_places, minters, total_supply_trackable, amount, expected = test_data + fav = FungibleAssetValue.from_raw_data(ticker, decimal_places, minters, total_supply_trackable, amount) + assert fav.serialized_plain_value == expected + + +def test_to_fungible_asset(): + assert FungibleAssetValue.to_fungible_asset("CRYSTAL", 100, 18) == [ + {"decimalPlaces": b'\x12', "minters": None, "ticker": "CRYSTAL"}, 100 * 10 ** 18] + assert FungibleAssetValue.to_fungible_asset("GARAGE", 1, 18) == [ + {"decimalPlaces": b'\x12', "minters": None, "ticker": "GARAGE", "totalSupplyTrackable": True}, 1 * 10 ** 18] + assert FungibleAssetValue.to_fungible_asset("OTHER", 999, 0) == [ + {"decimalPlaces": b'\x00', "minters": None, "ticker": "OTHER"}, 999] + + +def test_serialize(): + assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("CRYSTAL", 100, + 18)).hex() == "6c35323a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275373a4352595354414c65693130303030303030303030303030303030303030306565" + assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("GARAGE", 1, + 18)).hex() == "6c37363a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275363a4741524147457532303a746f74616c537570706c79547261636b61626c65746569313030303030303030303030303030303030306565" + assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("OTHER", 999, + 0)).hex() == "6c35303a647531333a646563696d616c506c61636573313a0075373a6d696e746572736e75363a7469636b657275353a4f5448455265693939396565" diff --git a/tests/lib9c/test_fungible_asset.py b/tests/lib9c/test_fungible_asset.py deleted file mode 100644 index 2536966d..00000000 --- a/tests/lib9c/test_fungible_asset.py +++ /dev/null @@ -1,13 +0,0 @@ -from common.lib9c.fungible_asset import FungibleAsset - - -def test_to_fungible_asset(): - assert FungibleAsset.to_fungible_asset("CRYSTAL", 100, 18) == [{"decimalPlaces": b'\x12', "minters": None, "ticker": "CRYSTAL"}, 100 * 10**18] - assert FungibleAsset.to_fungible_asset("GARAGE", 1, 18) == [{"decimalPlaces": b'\x12', "minters": None, "ticker": "GARAGE", "totalSupplyTrackable": True}, 1 * 10**18] - assert FungibleAsset.to_fungible_asset("OTHER", 999, 0) == [{"decimalPlaces": b'\x00', "minters": None, "ticker": "OTHER"}, 999] - - -def test_serialize(): - assert FungibleAsset.serialize(FungibleAsset.to_fungible_asset("CRYSTAL", 100, 18)).hex() == "6c35323a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275373a4352595354414c65693130303030303030303030303030303030303030306565" - assert FungibleAsset.serialize(FungibleAsset.to_fungible_asset("GARAGE", 1, 18)).hex() == "6c37363a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275363a4741524147457532303a746f74616c537570706c79547261636b61626c65746569313030303030303030303030303030303030306565" - assert FungibleAsset.serialize(FungibleAsset.to_fungible_asset("OTHER", 999, 0)).hex() == "6c35303a647531333a646563696d616c506c61636573313a0075373a6d696e746572736e75363a7469636b657275353a4f5448455265693939396565" From 4c32b67dc82b44223440b82deea751eff4df5ce4 Mon Sep 17 00:00:00 2001 From: hyeon Date: Thu, 11 Jan 2024 17:24:33 +0900 Subject: [PATCH 5/8] Make test passes --- common/utils/actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/utils/actions.py b/common/utils/actions.py index 27a8299d..16e19e9e 100644 --- a/common/utils/actions.py +++ b/common/utils/actions.py @@ -1,6 +1,6 @@ from typing import List, Optional, Dict, Any -from common.lib9c.fungible_asset import FungibleAsset +from common.lib9c.models.fungible_asset_value import FungibleAssetValue def create_unload_my_garages_action_plain_value(id: str, fav_data: List, avatar_addr: str, item_data: List, memo: Optional[str]) -> Dict[str, Any]: @@ -15,7 +15,7 @@ def create_unload_my_garages_action_plain_value(id: str, fav_data: List, avatar_ [ [ bytes.fromhex(x['balanceAddr'][2:] if x["balanceAddr"].startswith("0x") else x["balanceAddr"]), - FungibleAsset.to_fungible_asset(x['value']['currencyTicker'], int(x['value']['value']), int(x['value']['decimalPlaces'])) + FungibleAssetValue.to_fungible_asset(x['value']['currencyTicker'], int(x['value']['value']), int(x['value']['decimalPlaces'])) ] for x in fav_data ], From 2f798e1de79cff289017c0c8aee22524681199ba Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 12 Jan 2024 11:49:39 +0900 Subject: [PATCH 6/8] Add address tests for value error cases --- tests/lib9c/models/test_address.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/lib9c/models/test_address.py b/tests/lib9c/models/test_address.py index 9d19b306..dd247ed8 100644 --- a/tests/lib9c/models/test_address.py +++ b/tests/lib9c/models/test_address.py @@ -18,3 +18,15 @@ def test_address(addr): else: assert address.raw == bytes.fromhex(addr) assert address.short_format == addr.lower() + + +@pytest.mark.parametrize("addr", + [ + "0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27X", # Invalid character + "a5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27X", # Invalid character + "0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A2", # Length + "a5f7e0bd63AD2749D66380f36Eb33Fe0ba50A2", # Length + ]) +def test_address_error(addr): + with pytest.raises(ValueError) as e: + Address(addr) From 4963586b0a0481c59bdb76aabdc37f98c97496cd Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 12 Jan 2024 14:16:19 +0900 Subject: [PATCH 7/8] Make all codes use new code --- common/lib9c/models/currency.py | 29 ++----------------- common/lib9c/models/fungible_asset_value.py | 11 +------ common/utils/actions.py | 5 +++- .../lib9c/models/test_fungible_asset_value.py | 18 ------------ 4 files changed, 7 insertions(+), 56 deletions(-) diff --git a/common/lib9c/models/currency.py b/common/lib9c/models/currency.py index 66dea7a7..fe86a34e 100644 --- a/common/lib9c/models/currency.py +++ b/common/lib9c/models/currency.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import Dict, Union, List, Optional, Any + +from typing import Dict, List, Optional, Any import bencodex @@ -59,29 +60,3 @@ def plain_value(self) -> Dict[str, Any]: @property def serialized_plain_value(self) -> bytes: return bencodex.dumps(self.plain_value) - - @staticmethod - def to_currency_plain_value(ticker: str) -> Dict[str, Union[str, int, None]]: - if ticker.lower() == "crystal": - return { - "decimalPlaces": b'\x12', - "minters": None, - "ticker": "CRYSTAL", - } - elif ticker.lower() == "garage": - return { - "decimalPlaces": b'\x12', - "minters": None, - "ticker": "GARAGE", - "totalSupplyTrackable": True, - } - else: - return { - "decimalPlaces": b'\x00', - "minters": None, - "ticker": ticker.upper(), - } - - @staticmethod - def serialize(currency: Dict[str, Union[str, int, None]]) -> bytes: - return bencodex.dumps(currency) diff --git a/common/lib9c/models/fungible_asset_value.py b/common/lib9c/models/fungible_asset_value.py index 42c2c96c..6ded1705 100644 --- a/common/lib9c/models/fungible_asset_value.py +++ b/common/lib9c/models/fungible_asset_value.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, Union, List, Optional, Any +from typing import Dict, List, Optional, Any import bencodex @@ -33,12 +33,3 @@ def plain_value(self) -> List[Dict[str, Any] | float]: @property def serialized_plain_value(self) -> bytes: return bencodex.dumps(self.plain_value) - - @staticmethod - def to_fungible_asset(ticker: str, amount: int, decimal_places: int) \ - -> List[Union[Dict[str, Union[str, int, None]], int]]: - return [Currency.to_currency_plain_value(ticker), amount * max(1, 10 ** decimal_places)] - - @staticmethod - def serialize(fungible_asset: List[Union[Dict[str, Union[str, int, None]], int]]) -> bytes: - return bencodex.dumps([Currency.serialize(fungible_asset[0]), fungible_asset[1]]) diff --git a/common/utils/actions.py b/common/utils/actions.py index 16e19e9e..586a3569 100644 --- a/common/utils/actions.py +++ b/common/utils/actions.py @@ -15,7 +15,10 @@ def create_unload_my_garages_action_plain_value(id: str, fav_data: List, avatar_ [ [ bytes.fromhex(x['balanceAddr'][2:] if x["balanceAddr"].startswith("0x") else x["balanceAddr"]), - FungibleAssetValue.to_fungible_asset(x['value']['currencyTicker'], int(x['value']['value']), int(x['value']['decimalPlaces'])) + FungibleAssetValue.from_raw_data( + x["value"]["currencyTicker"], x["value"]["decimalPlaces"], x["value"].get("minters", None), + amount=x["value"]["value"] + ).plain_value ] for x in fav_data ], diff --git a/tests/lib9c/models/test_fungible_asset_value.py b/tests/lib9c/models/test_fungible_asset_value.py index d32ad0ab..56619a9d 100644 --- a/tests/lib9c/models/test_fungible_asset_value.py +++ b/tests/lib9c/models/test_fungible_asset_value.py @@ -68,21 +68,3 @@ def test_serialized_plain_value(test_data): ticker, decimal_places, minters, total_supply_trackable, amount, expected = test_data fav = FungibleAssetValue.from_raw_data(ticker, decimal_places, minters, total_supply_trackable, amount) assert fav.serialized_plain_value == expected - - -def test_to_fungible_asset(): - assert FungibleAssetValue.to_fungible_asset("CRYSTAL", 100, 18) == [ - {"decimalPlaces": b'\x12', "minters": None, "ticker": "CRYSTAL"}, 100 * 10 ** 18] - assert FungibleAssetValue.to_fungible_asset("GARAGE", 1, 18) == [ - {"decimalPlaces": b'\x12', "minters": None, "ticker": "GARAGE", "totalSupplyTrackable": True}, 1 * 10 ** 18] - assert FungibleAssetValue.to_fungible_asset("OTHER", 999, 0) == [ - {"decimalPlaces": b'\x00', "minters": None, "ticker": "OTHER"}, 999] - - -def test_serialize(): - assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("CRYSTAL", 100, - 18)).hex() == "6c35323a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275373a4352595354414c65693130303030303030303030303030303030303030306565" - assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("GARAGE", 1, - 18)).hex() == "6c37363a647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275363a4741524147457532303a746f74616c537570706c79547261636b61626c65746569313030303030303030303030303030303030306565" - assert FungibleAssetValue.serialize(FungibleAssetValue.to_fungible_asset("OTHER", 999, - 0)).hex() == "6c35303a647531333a646563696d616c506c61636573313a0075373a6d696e746572736e75363a7469636b657275353a4f5448455265693939396565" From fea7eacfd16d3f36ba6700601c17365d112ce821 Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 12 Jan 2024 14:16:26 +0900 Subject: [PATCH 8/8] Conventiont --- common/lib9c/models/currency.py | 10 +++++----- common/utils/actions.py | 5 +++-- tests/utils/test_actions.py | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/lib9c/models/currency.py b/common/lib9c/models/currency.py index fe86a34e..2baacf4c 100644 --- a/common/lib9c/models/currency.py +++ b/common/lib9c/models/currency.py @@ -22,12 +22,12 @@ def __init__(self, ticker: str, decimal_places: int, minters: Optional[List[str] self.decimal_places = decimal_places self.total_supply_trackable = total_supply_trackable - def __eq__(self, other:Currency): + def __eq__(self, other: Currency): return ( - self.ticker == other.ticker and - self.minters == other.minters and - self.decimal_places == other.decimal_places and - self.total_supply_trackable == other.total_supply_trackable + self.ticker == other.ticker and + self.minters == other.minters and + self.decimal_places == other.decimal_places and + self.total_supply_trackable == other.total_supply_trackable ) @classmethod diff --git a/common/utils/actions.py b/common/utils/actions.py index 586a3569..e87b1768 100644 --- a/common/utils/actions.py +++ b/common/utils/actions.py @@ -3,13 +3,14 @@ from common.lib9c.models.fungible_asset_value import FungibleAssetValue -def create_unload_my_garages_action_plain_value(id: str, fav_data: List, avatar_addr: str, item_data: List, memo: Optional[str]) -> Dict[str, Any]: +def create_unload_my_garages_action_plain_value(_id: str, fav_data: List, avatar_addr: str, item_data: List, + memo: Optional[str]) -> Dict[str, Any]: if avatar_addr.startswith("0x"): avatar_addr = avatar_addr[2:] return { 'type_id': 'unload_from_my_garages', 'values': { - 'id': bytes.fromhex(id), + 'id': bytes.fromhex(_id), "l": [ bytes.fromhex(avatar_addr), [ diff --git a/tests/utils/test_actions.py b/tests/utils/test_actions.py index 62f843c0..31378fa1 100644 --- a/tests/utils/test_actions.py +++ b/tests/utils/test_actions.py @@ -25,9 +25,9 @@ def test_create_unload_my_garages_action_plain_value(): "count": 200 } ] - id = "6e747fecdc33374a81fdc42b99d0d4f3" + _id = "6e747fecdc33374a81fdc42b99d0d4f3" memo = '["0x9eaac29af78f88f8dbb5fad976c683e92f25fdb3", "0x25b4ce744b7e0150ef9999b6eff5010b6d4a164a", "{\\"iap\\": {\\"g_sku\\": \\"g_pkg_launching1\\", \\"a_sku\\": \\"a_pkg_launching1\\"}}"]' - plain_value = create_unload_my_garages_action_plain_value(id, fav_data, avatar_addr, item_data, memo) + plain_value = create_unload_my_garages_action_plain_value(_id, fav_data, avatar_addr, item_data, memo) expected = {'type_id': 'unload_from_my_garages', 'values': {'id': b'nt\x7f\xec\xdc37J\x81\xfd\xc4+\x99\xd0\xd4\xf3', 'l': [b'A\xae\xfeL\xdd\xfbW\xc9\xdf\xfdI\x0e\x17\xe5qp\\Y=\xdc', [[b'\x1c*\xe9s\x80\xcf\xb4\xf72\x04\x9eEOm\x9a%\xd4\x96|o', [{'decimalPlaces': b'\x12', 'minters': None, 'ticker': 'CRYSTAL'}, 1500000000000000000000000]]], [[b'9\x91\xe0M\xd8\x08\xdc\x0b\xc2K!\xf5\xad\xb7\xbf\x19\x971/\x87\x00\xda\xf13K\xf3I6\xe8\xa0\x81:', 8000], [b'\xf8\xfa\xf9,\x9c\r\x0e\x8e\x06iCa\xea\x87\xbf\xc8\xb2\x9a\x8a\xe8\xde\x93\x04K\x98G\nWcn\xd0\xe0', 200]], '["0x9eaac29af78f88f8dbb5fad976c683e92f25fdb3", "0x25b4ce744b7e0150ef9999b6eff5010b6d4a164a", "{\\"iap\\": {\\"g_sku\\": \\"g_pkg_launching1\\", \\"a_sku\\": \\"a_pkg_launching1\\"}}"]']}} expected_hex = "6475373a747970655f69647532323a756e6c6f61645f66726f6d5f6d795f6761726167657375363a76616c7565736475323a696431363a6e747fecdc33374a81fdc42b99d0d4f375313a6c6c32303a41aefe4cddfb57c9dffd490e17e571705c593ddc6c6c32303a1c2ae97380cfb4f732049e454f6d9a25d4967c6f6c647531333a646563696d616c506c61636573313a1275373a6d696e746572736e75363a7469636b657275373a4352595354414c656931353030303030303030303030303030303030303030303030656565656c6c33323a3991e04dd808dc0bc24b21f5adb7bf1997312f8700daf1334bf34936e8a0813a693830303065656c33323af8faf92c9c0d0e8e06694361ea87bfc8b29a8ae8de93044b98470a57636ed0e069323030656565753137333a5b22307839656161633239616637386638386638646262356661643937366336383365393266323566646233222c2022307832356234636537343462376530313530656639393939623665666635303130623664346131363461222c20227b5c226961705c223a207b5c22675f736b755c223a205c22675f706b675f6c61756e6368696e67315c222c205c22615f736b755c223a205c22615f706b675f6c61756e6368696e67315c227d7d225d656565"