Data Structures
Contents
Data Structures¶
The following data structures are built with pydantic dataclass objects.
Note: The definitions below are ordered topologically to facilitate execution of the BIP15 spec.
Misc dependencies¶
Config
¶
Global dataclass config settings inherited by all dataclasses.
class Config:
validate_assignment = True
arbitrary_types_allowed = True
GlobalSettings
¶
@dataclass(config=Config)
class GlobalSettings:
"""
Represents the default global settings. These can be overridden by the BancorDapp configuration upon instantiation.
"""
timestamp: int = DEFAULT_TIMESTAMP
model: str = MODEL
version: str = VERSION
eightee_places: int = DEFAULT_QDECIMALS
max_uint112: int = MAX_UINT112
precision: int = PRECISION
decimals: int = DEFAULT_DECIMALS
whitelisted_tokens: List[str] = field(default_factory=lambda: DEFAULT_WHITELIST)
active_users: List[str] = field(default_factory=lambda: DEFAULT_USERS)
price_feeds_path: str = DEFAULT_PRICE_FEEDS_PATH
cooldown_time: int = DEFAULT_COOLDOWN_TIME
network_fee: Decimal = DEFAULT_NETWORK_FEE
withdrawal_fee: Decimal = DEFAULT_WITHDRAWAL_FEE
bnt_min_liquidity: Decimal = DEFAULT_BNT_MIN_LIQUIDITY
trading_fee: Decimal = DEFAULT_TRADING_FEE
bnt_funding_limit: Decimal = DEFAULT_BNT_FUNDING_LIMIT
alpha: Decimal = DEFAULT_ALPHA
Cooldown
¶
@dataclass(config=Config)
class Cooldown:
"""
Represents a pending withdrawal cooldown.
"""
id: int
created_at: int
user_name: str
tkn_name: str
is_complete: bool
tkn: Any = field(default_factory=Token)
pooltoken: Any = field(default_factory=Token)
StandardProgram
¶
@dataclass(config=Config)
class StandardProgram:
"""
Represents an standard reward program state.
"""
id: int
tkn_name: str
rewards_token: str
is_enabled: bool
is_active: bool
start_time: int
end_time: int
last_update_time: int
reward_rate: Decimal
remaining_rewards: Any = field(default_factory=Token)
reward_per_token: Any = field(default_factory=Token)
total_unclaimed_rewards: Any = field(default_factory=Token)
staked_reward_amt: Any = field(default_factory=Token)
pooltoken_amt: Any = field(default_factory=Token)
providers: list = field(default_factory=list)
AutocompoundingProgram
¶
@dataclass(config=Config)
class AutocompoundingProgram:
"""
Represents an autocompounding reward program state.
"""
id: int
tkn_name: str
owner_id: str
half_life_days: int
start_time: int
created_at: int
_half_life_seconds: int = 0
total_rewards: Any = field(default_factory=Token)
remaining_rewards: Any = field(default_factory=Token)
prev_token_amt_distributed: Any = field(default_factory=Token)
total_duration_in_seconds: Decimal = Decimal("0")
distribution_type: str = "exp"
is_active: bool = False
is_enabled: bool = False
@property
def flat_distribution_rate_per_second(self):
"""
Returns the rate per second of the distribution.
"""
return (
self.total_rewards.balance.quantize(DEFAULT_QDECIMALS)
/ self.total_duration_in_seconds
)
@property
def half_life_seconds(self):
"""
Returns the half-life of the distribution in seonds.
"""
return (
self.half_life_days * SECONDS_PER_DAY
if self._half_life_seconds == 0
else self._half_life_seconds
)
@half_life_seconds.setter
def half_life_seconds(self, value):
"""
Sets the half-life of the distribution in seonds.
"""
self._half_life_seconds = value
UserStandardProgram
¶
@dataclass(config=Config)
class UserStandardProgram:
"""
Represents a standard reward program user state
"""
id: int
staked_amt: Any = field(default_factory=Token)
pending_rewards: Any = field(default_factory=Token)
reward_per_token_paid: Any = field(default_factory=Token)
User
¶
@dataclass(config=Config)
class User:
"""
Represents a user agent state.
"""
user_name: str
pending_withdrawals: Dict[int, Cooldown] = field(
default_factory=lambda: defaultdict(Cooldown)
)
pending_standard_rewards: Dict[int, UserStandardProgram] = field(
default_factory=lambda: defaultdict(UserStandardProgram)
)
wallet: Dict[str, Token] = field(default_factory=lambda: defaultdict(Token))
Tokens
¶
@dataclass(config=Config)
class Tokens(GlobalSettings):
"""
Represents all ledger and other configuration balances associated with a particular token's current state.
"""
timestamp: int = DEFAULT_TIMESTAMP
master_vault: Any = field(default_factory=Token)
staking_ledger: Any = field(default_factory=Token)
pooltoken_supply: Any = field(default_factory=Token)
protocol_wallet_pooltokens: Any = field(default_factory=Token)
vortex_ledger: Any = field(default_factory=Token)
vbnt_burned: Any = field(default_factory=Token)
external_protection_vault: Any = field(default_factory=Token)
standard_rewards_vault: Any = field(default_factory=Token)
bnt_trading_liquidity: Any = field(default_factory=Token)
tkn_trading_liquidity: Any = field(default_factory=Token)
bnt_funding_limit: Decimal = DEFAULT_BNT_FUNDING_LIMIT
bnt_funding_amt: Any = field(default_factory=Token)
_vbnt_price: Any = field(default_factory=Token)
spot_rate: Decimal = Decimal("0")
inv_spot_rate: Decimal = Decimal("0")
ema_rate: Decimal = Decimal("0")
ema_last_updated: Decimal = Decimal("0")
_inv_ema_rate: Decimal = Decimal("0")
is_trading_enabled: bool = False
@property
def bnt_remaining_funding(self):
"""
Computes the BNT funding remaining for the pool.
"""
return self.bnt_funding_limit - self.bnt_funding_amt.balance.quantize(DEFAULT_QDECIMALS)
@property
def vbnt_price(self):
"""
Returns the price of the current vbnt token. Only valid when name==bnt
"""
assert (
self.name == "bnt"
), f"vbnt_price attempted to be accessed in {self.name} state, call bnt state instead"
return self._vbnt_price
@property
def is_price_stable(self):
"""
True if the spot price deviation from the EMA is less than 1% (or other preset threshold amount).
"""
return (
Decimal("0.99") * self.ema_rate
<= self.spot_rate
<= Decimal("1.01") * self.ema_rate
)
@property
def avg_tkn_trading_liquidity(self):
"""
The tkn trading liquidity adjusted by the ema.
"""
return (
self.bnt_trading_liquidity.balance.quantize(DEFAULT_QDECIMALS) / self.ema_rate
if self.ema_rate > 0
else 0
)
@property
def tkn_excess(self):
"""
The difference between the master_vault balance and the average trading liquidity.
"""
return self.master_vault.balance.quantize(DEFAULT_QDECIMALS) - self.avg_tkn_trading_liquidity
@property
def tkn_excess_bnt_equivalence(self):
"""
Computes the equivalent bnt value of the non-trading tkn balance of the master_vault.
"""
return self.tkn_excess * self.ema_rate
@property
def bnt_bootstrap_liquidity(self):
"""
Computes the bnt_min_liquidity multiplied by 2.
"""
return 2 * self.bnt_min_liquidity
@property
def inv_ema_rate(self) -> Decimal:
"""
The inverse EMA rate.
"""
return self._inv_ema_rate
@inv_ema_rate.setter
def inv_ema_rate(self, val):
"""
Sets a new inverse EMA rate value.
"""
self._inv_ema_rate = (self.inv_spot_rate * Decimal(0.2)) + (Decimal(0.8) * val)
@property
def inv_ema(self) -> Fraction:
"""
Returns a fraction as two separate outputs
"""
return Fraction(self.inv_ema_rate)
@property
def ema(self) -> Fraction:
"""
Returns a fraction as two separate outputs
"""
return Fraction(self.ema_rate)
@property
def ema_descale(self) -> int:
"""
Used for descaling the ema into at most 112 bits per component.
"""
return (
int(max(self.ema.numerator, self.ema.denominator)) + self.max_uint112 - 1
) // self.max_uint112
@property
def ema_compressed_numerator(self) -> int:
"""
Used to measure the deviation of solidity fixed point math on v3 calclulations.
"""
return int(self.ema.numerator / self.ema_descale)
@property
def ema_compressed_denominator(self) -> int:
"""
Used to measure the deviation of solidity fixed point math on v3 calclulations.
"""
return int(self.ema.denominator / self.ema_descale)
@property
def is_ema_update_allowed(self) -> bool:
"""
Returns True if the moving average has not been updated on the existing block.
"""
return int(self.timestamp) != int(self.ema_last_updated)
@property
def ema_deviation(self) -> Decimal:
"""
Returns the deviation between these values as emaRate/emaCompressedRate.
"""
if self.ema_compressed_numerator > 0:
return self.ema_rate * Decimal(
self.ema_compressed_denominator / self.ema_compressed_numerator
)
else:
return Decimal("0")
System State¶
State
¶
@dataclass(config=Config)
class State(GlobalSettings):
"""
Represents the system state at the current timestamp. Main interface for all other dataclasses.
"""
transaction_id: int = 0
timestamp: int = DEFAULT_TIMESTAMP
price_feeds: PandasDataFrame = None
whitelisted_tokens: list = field(default_factory=list)
tokens: Dict[str, Tokens] = field(
default_factory=lambda: defaultdict(Tokens)
)
users: Dict[str, User] = field(default_factory=lambda: defaultdict(User))
standard_reward_programs: Dict[int, StandardProgram] = field(
default_factory=lambda: defaultdict(StandardProgram)
)
autocompounding_reward_programs: Dict[str, AutocompoundingProgram] = field(
default_factory=lambda: defaultdict(AutocompoundingProgram)
)
history: list = field(default_factory=list)
logger: Any = logger
json_export: dict = field(default_factory=dict)
@property
def valid_rewards_programs(self):
"""
Returns all valid autocompounding programs for the current state.
"""
return [
p
for p in self.autocompounding_reward_programs
if self.autocompounding_reward_programs[p].is_active
and self.autocompounding_reward_programs[p].is_enabled
]
@property
def usernames(self):
"""
Returns a list of all current users
"""
return [user for user in self.users]
@property
def withdrawal_ids(self):
"""
Returns a list of all withdrawal_ids
"""
return sum([len(self.users[user].pending_withdrawals) for user in self.users])
@property
def autocompounding_programs_count(self) -> int:
"""
Returns the count of active autocompounding reward programs
"""
return len(
[
self.autocompounding_reward_programs[tkn_name]
for tkn_name in self.autocompounding_reward_programs
if self.autocompounding_reward_programs[tkn_name].is_active
]
)
@property
def active_autocompounding_programs(self) -> list:
"""
Returns the active autocompounding reward programs
"""
return [tkn_name for tkn_name in self.autocompounding_reward_programs if
self.autocompounding_reward_programs[tkn_name].is_active]
@property
def active_standard_programs(self) -> list:
"""
Returns the active standard reward programs
"""
return [tkn_name for tkn_name in self.whitelisted_tokens if self.standard_reward_programs[tkn_name].is_active]
@property
def standard_programs_count(self) -> int:
"""
Returns the count of active standard reward programs
"""
return len(
[
self.standard_reward_programs[tkn_name]
for tkn_name in self.whitelisted_tokens
if self.standard_reward_programs[tkn_name].is_active
]
)
@property
def bnt_price(self) -> Decimal:
"""
Returns the bnt price feed at the current timestamp.
"""
return Decimal(self.price_feeds.at[self.timestamp, "bnt"])
@property
def bnt_virtual_balance(self) -> Decimal:
"""
Returns the inverse of the bnt price feed at the current timestamp
"""
return Decimal("1") / self.bnt_price
@property
def bnbnt_rate(self) -> Decimal:
"""
Returns the inverse of the bnt price feed at the current timestamp
"""
if (
self.tokens["bnt"].staking_ledger.balance.quantize(DEFAULT_QDECIMALS) == 0
and self.tokens["bnt"].pooltoken_supply.balance.quantize(DEFAULT_QDECIMALS) == 0
):
bnbnt_rate = Decimal("1")
else:
bnbnt_rate = Decimal(
self.tokens["bnt"].pooltoken_supply.balance.quantize(DEFAULT_QDECIMALS) / self.tokens[
"bnt"].staking_ledger.balance.quantize(DEFAULT_QDECIMALS)
)
return bnbnt_rate