167 lines
6.7 KiB
Python
167 lines
6.7 KiB
Python
# # ⚠ Warning
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
# [🥭 Mango Markets](https://mango.markets/) support is available at:
|
|
# [Docs](https://docs.mango.markets/)
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
|
import datetime
|
|
import logging
|
|
|
|
from decimal import Decimal
|
|
from solana.publickey import PublicKey
|
|
|
|
from .accountinfo import AccountInfo
|
|
from .addressableaccount import AddressableAccount
|
|
from .context import Context
|
|
from .layouts import layouts
|
|
from .version import Version
|
|
|
|
|
|
# # 🥭 AggregatorConfig class
|
|
#
|
|
|
|
class AggregatorConfig:
|
|
def __init__(self, version: Version, description: str, decimals: Decimal, restart_delay: Decimal,
|
|
max_submissions: Decimal, min_submissions: Decimal, reward_amount: Decimal,
|
|
reward_token_account: PublicKey):
|
|
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
|
self.version: Version = version
|
|
self.description: str = description
|
|
self.decimals: Decimal = decimals
|
|
self.restart_delay: Decimal = restart_delay
|
|
self.max_submissions: Decimal = max_submissions
|
|
self.min_submissions: Decimal = min_submissions
|
|
self.reward_amount: Decimal = reward_amount
|
|
self.reward_token_account: PublicKey = reward_token_account
|
|
|
|
@staticmethod
|
|
def from_layout(layout: layouts.AGGREGATOR_CONFIG) -> "AggregatorConfig":
|
|
return AggregatorConfig(Version.UNSPECIFIED, layout.description, layout.decimals,
|
|
layout.restart_delay, layout.max_submissions, layout.min_submissions,
|
|
layout.reward_amount, layout.reward_token_account)
|
|
|
|
def __str__(self) -> str:
|
|
return f"« AggregatorConfig: '{self.description}', Decimals: {self.decimals} [restart delay: {self.restart_delay}], Max: {self.max_submissions}, Min: {self.min_submissions}, Reward: {self.reward_amount}, Reward Account: {self.reward_token_account} »"
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self}"
|
|
|
|
|
|
# # 🥭 Round class
|
|
#
|
|
|
|
|
|
class Round:
|
|
def __init__(self, version: Version, id: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
|
|
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
|
self.version: Version = version
|
|
self.id: Decimal = id
|
|
self.created_at: datetime.datetime = created_at
|
|
self.updated_at: datetime.datetime = updated_at
|
|
|
|
@staticmethod
|
|
def from_layout(layout: layouts.ROUND) -> "Round":
|
|
return Round(Version.UNSPECIFIED, layout.id, layout.created_at, layout.updated_at)
|
|
|
|
def __str__(self) -> str:
|
|
return f"« Round[{self.id}], Created: {self.updated_at}, Updated: {self.updated_at} »"
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self}"
|
|
|
|
|
|
# # 🥭 Answer class
|
|
#
|
|
|
|
class Answer:
|
|
def __init__(self, version: Version, round_id: Decimal, median: Decimal, created_at: datetime.datetime, updated_at: datetime.datetime):
|
|
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
|
self.version: Version = version
|
|
self.round_id: Decimal = round_id
|
|
self.median: Decimal = median
|
|
self.created_at: datetime.datetime = created_at
|
|
self.updated_at: datetime.datetime = updated_at
|
|
|
|
@staticmethod
|
|
def from_layout(layout: layouts.ANSWER) -> "Answer":
|
|
return Answer(Version.UNSPECIFIED, layout.round_id, layout.median, layout.created_at, layout.updated_at)
|
|
|
|
def __str__(self) -> str:
|
|
return f"« Answer: Round[{self.round_id}], Median: {self.median:,.8f}, Created: {self.updated_at}, Updated: {self.updated_at} »"
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self}"
|
|
|
|
# # 🥭 Aggregator class
|
|
#
|
|
|
|
|
|
class Aggregator(AddressableAccount):
|
|
def __init__(self, account_info: AccountInfo, version: Version, config: AggregatorConfig,
|
|
initialized: bool, name: str, owner: PublicKey, round_: Round,
|
|
round_submissions: PublicKey, answer: Answer, answer_submissions: PublicKey):
|
|
super().__init__(account_info)
|
|
self.version: Version = version
|
|
self.config: AggregatorConfig = config
|
|
self.initialized: bool = initialized
|
|
self.name: str = name
|
|
self.owner: PublicKey = owner
|
|
self.round: Round = round_
|
|
self.round_submissions: PublicKey = round_submissions
|
|
self.answer: Answer = answer
|
|
self.answer_submissions: PublicKey = answer_submissions
|
|
|
|
@property
|
|
def price(self) -> Decimal:
|
|
return self.answer.median / (10 ** self.config.decimals)
|
|
|
|
@staticmethod
|
|
def from_layout(layout: layouts.AGGREGATOR, account_info: AccountInfo, name: str) -> "Aggregator":
|
|
config = AggregatorConfig.from_layout(layout.config)
|
|
initialized = bool(layout.initialized)
|
|
round_ = Round.from_layout(layout.round)
|
|
answer = Answer.from_layout(layout.answer)
|
|
|
|
return Aggregator(account_info, Version.UNSPECIFIED, config, initialized, name, layout.owner,
|
|
round_, layout.round_submissions, answer, layout.answer_submissions)
|
|
|
|
@staticmethod
|
|
def parse(context: Context, account_info: AccountInfo) -> "Aggregator":
|
|
data = account_info.data
|
|
if len(data) != layouts.AGGREGATOR.sizeof():
|
|
raise Exception(f"Data length ({len(data)}) does not match expected size ({layouts.AGGREGATOR.sizeof()})")
|
|
|
|
name = context.lookup_oracle_name(account_info.address)
|
|
layout = layouts.AGGREGATOR.parse(data)
|
|
return Aggregator.from_layout(layout, account_info, name)
|
|
|
|
@staticmethod
|
|
def load(context: Context, account_address: PublicKey):
|
|
account_info = AccountInfo.load(context, account_address)
|
|
if account_info is None:
|
|
raise Exception(f"Aggregator account not found at address '{account_address}'")
|
|
return Aggregator.parse(context, account_info)
|
|
|
|
def __str__(self) -> str:
|
|
return f"""
|
|
« Aggregator '{self.name}' [{self.version}]:
|
|
Config: {self.config}
|
|
Initialized: {self.initialized}
|
|
Owner: {self.owner}
|
|
Round: {self.round}
|
|
Round Submissions: {self.round_submissions}
|
|
Answer: {self.answer}
|
|
Answer Submissions: {self.answer_submissions}
|
|
»
|
|
"""
|