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

feat: Publishing offchain future assets #109

Merged
merged 27 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1a2c644
feat(perp_assets): Pushing to test CI
akhercha Jun 11, 2024
9571586
feat(perp_assets): Fixed test
akhercha Jun 11, 2024
22e8e46
feat(perp_assets): Fixed test
akhercha Jun 11, 2024
dcd8656
feat(perp_assets): publishing test change
akhercha Jun 11, 2024
cd3e03d
feat(perp_assets): Publishing future entry
akhercha Jun 11, 2024
66b7d15
feat(perp_assets): Exclude errors from entries
akhercha Jun 11, 2024
319dc69
feat(perp_assets): Updates
akhercha Jun 11, 2024
5ccb128
feat(perp_assets): Allow publish with 2 types of entries
akhercha Jun 11, 2024
3b04736
feat(perp_assets): revert offchain app
akhercha Jun 11, 2024
fedfeb4
feat(perp_assets): Refinement
akhercha Jun 11, 2024
23faefd
feat(perp_assets): Fix from review
akhercha Jun 11, 2024
98c4d36
feat(perp_assets): Removed PerpFetcher
akhercha Jun 12, 2024
890b165
feat(perp_assets): Fixed bug
akhercha Jun 12, 2024
984ad0d
feat(perp_assets): Default value should be zero for expiry
akhercha Jun 14, 2024
dcb2ea6
feat(perp_assets): Fixed conflicts
akhercha Jun 14, 2024
cfe42dc
feat(perp_assets): Updated PragmaAPIClient
akhercha Jun 14, 2024
baaf906
feat(perp_assets): expiry not optional
akhercha Jun 14, 2024
25a6b03
feat(perp_assets): Refinements
akhercha Jun 14, 2024
b589256
feat(perp_assets): Quick refactoring
akhercha Jun 14, 2024
d7eb39f
feat(perp_assets): Publishing job
akhercha Jun 14, 2024
f86c643
feat(perp_assets): Fixed lint
akhercha Jun 15, 2024
4685292
feat(perp_assets): fixed test
akhercha Jun 15, 2024
15bbf53
feat(perp_assets): Ruff + Mypy lint
akhercha Jun 15, 2024
e85db77
feat(perp_assets): Fixed test
akhercha Jun 15, 2024
53aaa79
feat(perp_assets): Fixed stagecoach
akhercha Jun 15, 2024
2099fd7
feat(perp_assets): Removed debug print
akhercha Jun 15, 2024
56e8418
bump version to 1.4.0
EvolveArt Jun 15, 2024
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ cover/
devnet.pkl

.idea/
.vscode/
.vscode/

# Vscode workspaces
*.code-workspace
153 changes: 44 additions & 109 deletions pragma/core/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,30 @@

class Entry(abc.ABC):
@abc.abstractmethod
def serialize(self) -> Dict[str, str]: ...
def to_tuple(self) -> Tuple: ...

@abc.abstractmethod
def to_tuple(self) -> Tuple: ...
def serialize(self) -> Dict[str, str]: ...

@abc.abstractclassmethod
def offchain_serialize(self) -> Dict[str, str]: ...

@staticmethod
def serialize_entries(entries: List[Entry]) -> List[Dict[str, int]]:
serialized_entries = [
entry.serialize() for entry in entries if issubclass(entry, Entry)
entry.serialize() for entry in entries if isinstance(entry, Entry)
]
return list(filter(lambda item: item is not None, serialized_entries))

@staticmethod
def flatten_entries(entries: List[SpotEntry]) -> List[int]:
def offchain_serialize_entries(entries: List[Entry]) -> List[Dict[str, int]]:
serialized_entries = [

Check warning on line 29 in pragma/core/entry.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/entry.py#L29

Added line #L29 was not covered by tests
entry.offchain_serialize() for entry in entries if isinstance(entry, Entry)
]
return list(filter(lambda item: item is not None, serialized_entries))

Check warning on line 32 in pragma/core/entry.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/entry.py#L32

Added line #L32 was not covered by tests

@staticmethod
def flatten_entries(entries: List[Entry]) -> List[int]:
"""This flattens entriees to tuples. Useful when you need the raw felt array"""
expanded = [entry.to_tuple() for entry in entries]
flattened = [x for entry in expanded for x in entry]
Expand Down Expand Up @@ -167,30 +177,6 @@
autoscale_volume=False,
)

@staticmethod
def serialize_entries(entries: List[SpotEntry]) -> List[Dict[str, int]]:
"""serialize entries to a List of dictionaries"""
# TODO (#000): log errors
serialized_entries = [
entry.serialize()
for entry in entries
# TODO (#000): This needs to be much more resilient to publish errors
if isinstance(entry, SpotEntry)
]
return list(filter(lambda item: item is not None, serialized_entries))

@staticmethod
def offchain_serialize_entries(entries: List[SpotEntry]) -> List[Dict[str, int]]:
"""serialize entries to a List of dictionaries for off-chain consumption"""
# TODO (#000): log errors
serialized_entries = [
entry.offchain_serialize()
for entry in entries
# TODO (#000): This needs to be much more resilient to publish errors
if isinstance(entry, SpotEntry)
]
return list(filter(lambda item: item is not None, serialized_entries))

def __repr__(self):
return (
f'SpotEntry(pair_id="{felt_to_str(self.pair_id)}", '
Expand All @@ -204,14 +190,17 @@
"""
Represents a Future Entry.

Also used to represent a Perp Entry - the only difference is that a perpetual future has no
expiry timestamp.

⚠️ By default, the constructor will autoscale the provided volume to be quoted in the base asset.
This behavior can be overwritten witht the `autoscale_volume` parameter.
"""

base: BaseEntry
pair_id: int
price: int
expiry_timestamp: int
expiry_timestamp: Optional[int]
volume: int

def __init__(
Expand All @@ -221,7 +210,7 @@
timestamp: int,
source: Union[str, int],
publisher: Union[str, int],
expiry_timestamp: int,
expiry_timestamp: Optional[int] = None,
volume: Optional[float] = 0,
autoscale_volume: bool = True,
):
Expand Down Expand Up @@ -297,31 +286,19 @@
"volume": self.volume,
}

@staticmethod
def from_dict(entry_dict: Dict[str, str]) -> "FutureEntry":
base = dict(entry_dict["base"])
return FutureEntry(
entry_dict["pair_id"],
entry_dict["price"],
base["timestamp"],
base["source"],
base["publisher"],
entry_dict["expiration_timestamp"],
volume=entry_dict["volume"],
autoscale_volume=False,
)

@staticmethod
def serialize_entries(entries: List[FutureEntry]) -> List[Dict[str, int]]:
"""serialize entries to a List of dictionaries"""
# TODO (#000): log errors
serialized_entries = [
entry.serialize()
for entry in entries
# TODO (#000): This needs to be much more resilient to publish errors
if isinstance(entry, FutureEntry)
]
return list(filter(lambda item: item is not None, serialized_entries))
def offchain_serialize(self) -> Dict[str, str]:
serialized = {

Check warning on line 290 in pragma/core/entry.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/entry.py#L290

Added line #L290 was not covered by tests
"base": {
"timestamp": self.base.timestamp,
"source": felt_to_str(self.base.source),
"publisher": felt_to_str(self.base.publisher),
},
"pair_id": felt_to_str(self.pair_id),
"price": self.price,
"volume": self.volume,
"expiration_timestamp": self.expiry_timestamp,
}
return serialized

Check warning on line 301 in pragma/core/entry.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/entry.py#L301

Added line #L301 was not covered by tests

def __repr__(self):
return (
Expand All @@ -334,58 +311,16 @@
f'expiry_timestamp={self.expiry_timestamp})")'
)


class GenericEntry(Entry):
base: BaseEntry
key: int
value: int

def __init__(
self,
timestamp: int,
source: Union[str, int],
publisher: Union[str, int],
key: Union[str, int],
value: int,
):
if isinstance(publisher, str):
publisher = str_to_felt(publisher)

if isinstance(source, str):
source = str_to_felt(source)

if isinstance(key, str):
key = str_to_felt(key)

self.base = BaseEntry(timestamp, source, publisher)
self.key = key
self.value = value

def serialize(self) -> Dict[str, str]:
return {
"base": {
"timestamp": self.base.timestamp,
"source": self.base.source,
"publisher": self.base.publisher,
},
"key": self.key,
"value": self.value,
}

def to_tuple(self) -> Tuple:
return (
self.base.timestamp,
self.base.source,
self.base.publisher,
self.key,
self.value,
)

def __repr__(self):
return (
f'GenericEntry(key="{felt_to_str(self.key)}", '
f"value={self.value}, "
f"timestamp={self.base.timestamp}, "
f'source="{felt_to_str(self.base.source)}", '
f'publisher="{felt_to_str(self.base.publisher)}")'
@staticmethod
def from_dict(entry_dict: Dict[str, str]) -> "FutureEntry":
base = dict(entry_dict["base"])
return FutureEntry(

Check warning on line 317 in pragma/core/entry.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/entry.py#L316-L317

Added lines #L316 - L317 were not covered by tests
entry_dict["pair_id"],
entry_dict["price"],
base["timestamp"],
base["source"],
base["publisher"],
entry_dict["expiration_timestamp"],
volume=entry_dict["volume"],
autoscale_volume=False,
)
56 changes: 33 additions & 23 deletions pragma/core/mixins/offchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from starknet_py.net.client import Client
from starknet_py.utils.typed_data import TypedData

from pragma.core.entry import SpotEntry
from pragma.core.entry import Entry, FutureEntry, SpotEntry
from pragma.core.types import AggregationMode
from pragma.core.utils import exclude_none_and_exceptions

logger = logging.getLogger(__name__)

Expand All @@ -26,25 +27,14 @@
],
)

"""
{'base':
{'publisher': 88314212732225,
'source': 5787760245619121969, 'timestamp': 1697147959},
'pair_id': 19514442401534788, '
price': 1000,
'volume': 0}
"""


def build_publish_message(entries: List[SpotEntry], now: int, expiry: int) -> TypedData:
def build_publish_message(entries: List[Entry], for_future_entries: bool) -> TypedData:
message = {
"domain": {"name": "Pragma", "version": "1"},
"primaryType": "Request",
"message": {
"action": "Publish",
"entries": SpotEntry.serialize_entries(entries),
# "timestamp": now,
# "expiration": expiry,
"entries": Entry.serialize_entries(entries),
},
"types": {
"StarkNetDomain": [
Expand All @@ -68,7 +58,10 @@
],
},
}

if for_future_entries:
message["types"]["Entry"] = message["types"]["Entry"] + [

Check warning on line 62 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L61-L62

Added lines #L61 - L62 were not covered by tests
{"name": "expiration_timestamp", "type": "felt"},
]
return message


Expand All @@ -79,13 +72,11 @@
ssl_context: ssl.SSLContext
api_key: str

def sign_publish_message(
self, entries: List[SpotEntry], now: int, expiry: int
) -> (List[int], int):
def sign_publish_message(self, entries: List[Entry]) -> (List[int], int):
"""
Sign a publish message
"""
message = build_publish_message(entries, now, expiry)
message = build_publish_message(entries)

Check warning on line 79 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L79

Added line #L79 was not covered by tests
hash_ = TypedData.from_dict(message).message_hash(self.account.address)
sig = self.account.sign_message(message)

Expand All @@ -104,20 +95,37 @@

async def publish_data(
self,
entries: List[SpotEntry],
entries: List[Entry],
):
"""
Publish data to PragmAPI

Args:
entries (List[SpotEntry]): List of SpotEntry to publish
entries (List[Entry]): List of Entry to publish
"""
# TODO: We sometimes have some Error types in entries, we need to
# investigate why and don't push them in our entries.
# Currently, we only exclude them from here.
entries = exclude_none_and_exceptions(entries)

Check warning on line 109 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L109

Added line #L109 was not covered by tests

spot_entries = [entry for entry in entries if isinstance(entry, SpotEntry)]
future_entries = [entry for entry in entries if isinstance(entry, FutureEntry)]

Check warning on line 112 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L111-L112

Added lines #L111 - L112 were not covered by tests

spot_response = self._publish_entries(spot_entries)
future_response = self._publish_entries(future_entries, is_future=True)

Check warning on line 115 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L114-L115

Added lines #L114 - L115 were not covered by tests

return spot_response, future_response

Check warning on line 117 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L117

Added line #L117 was not covered by tests

async def _publish_entries(self, entries: List[Entry], is_future: bool = False):
# Check if all entries are of the same type
EntryClass = type(entries[0])
assert all(isinstance(entry, EntryClass) for entry in entries)

Check warning on line 122 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L121-L122

Added lines #L121 - L122 were not covered by tests

now = int(time.time())
expiry = now + 24 * 60 * 60

# Sign message
sig, _ = self.sign_publish_message(entries, now, expiry)
sig, _ = self.sign_publish_message(entries, is_future)

Check warning on line 128 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L128

Added line #L128 was not covered by tests

# Add headers
headers: Dict = {
Expand All @@ -128,10 +136,12 @@

body = {
"signature": [str(s) for s in sig],
"entries": SpotEntry.offchain_serialize_entries(entries),
"entries": EntryClass.offchain_serialize_entries(entries),
}

url = self.api_url + "/v1/data/publish"
if isinstance(entries[0], FutureEntry):
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
url += "_future"

Check warning on line 144 in pragma/core/mixins/offchain.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/offchain.py#L143-L144

Added lines #L143 - L144 were not covered by tests

logger.info(f"POST {url}")
logger.info(f"Headers: {headers}")
Expand Down
6 changes: 4 additions & 2 deletions pragma/core/mixins/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
return

invocations = []
serialized_spot_entries = SpotEntry.serialize_entries(entries)
spot_entries = [entry for entry in entries if isinstance(entry, SpotEntry)]
serialized_spot_entries = SpotEntry.serialize_entries(spot_entries)

Check warning on line 82 in pragma/core/mixins/oracle.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/oracle.py#L81-L82

Added lines #L81 - L82 were not covered by tests
if pagination:
index = 0
while index < len(serialized_spot_entries):
Expand Down Expand Up @@ -112,7 +113,8 @@
hex(invocation.hash),
)

serialized_future_entries = FutureEntry.serialize_entries(entries)
future_entries = [entry for entry in entries if isinstance(entry, FutureEntry)]
serialized_future_entries = FutureEntry.serialize_entries(future_entries)

Check warning on line 117 in pragma/core/mixins/oracle.py

View check run for this annotation

Codecov / codecov/patch

pragma/core/mixins/oracle.py#L116-L117

Added lines #L116 - L117 were not covered by tests
if pagination:
index = 0
while index < len(serialized_future_entries):
Expand Down
Loading
Loading