Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor lib9c models #216

Merged
merged 9 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions common/_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
31 changes: 0 additions & 31 deletions common/lib9c/currency.py

This file was deleted.

16 changes: 0 additions & 16 deletions common/lib9c/fungible_asset.py

This file was deleted.

24 changes: 24 additions & 0 deletions common/lib9c/models/address.py
Original file line number Diff line number Diff line change
@@ -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
62 changes: 62 additions & 0 deletions common/lib9c/models/currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from __future__ import annotations

from typing import Dict, 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)
35 changes: 35 additions & 0 deletions common/lib9c/models/fungible_asset_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations

from typing import Dict, 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)
12 changes: 8 additions & 4 deletions common/utils/actions.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
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]:
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),
[
[
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.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
],
Expand Down
32 changes: 32 additions & 0 deletions tests/lib9c/models/test_address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from common.lib9c.models.address import Address


@pytest.mark.parametrize("addr",
["0xa5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D",
"0xb3cbca0e64aeb4b5b861047fe1db5a1bec1c241f",
"a5f7e0bd63AD2749D66380f36Eb33Fe0ba50A27D",
"b3cbca0e64aeb4b5b861047fe1db5a1bec1c241f",
])
def test_address(addr):
address = Address(addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValueError 발생 케이스를 추가하면 좋을것 같습니다.

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()


@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)
58 changes: 58 additions & 0 deletions tests/lib9c/models/test_currency.py
Original file line number Diff line number Diff line change
@@ -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
70 changes: 70 additions & 0 deletions tests/lib9c/models/test_fungible_asset_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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
Loading