Skip to content

Commit

Permalink
OCT-1569 & OCT-1567 Change overhaul formulas (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
aziolek authored Apr 22, 2024
2 parents 7329109 + 6d8b6b9 commit 019fea7
Show file tree
Hide file tree
Showing 25 changed files with 281 additions and 81 deletions.
27 changes: 18 additions & 9 deletions backend/app/engine/octant_rewards/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,27 @@
from app.engine.octant_rewards.locked_ratio import LockedRatio
from app.engine.octant_rewards.locked_ratio.default import DefaultLockedRatio
from app.engine.octant_rewards.matched import MatchedRewards
from app.engine.octant_rewards.matched.with_ppf import MatchedRewardsWithPPF
from app.engine.octant_rewards.matched.percentage_from_staking import (
PercentageMatchedRewards,
)
from app.engine.octant_rewards.operational_cost import OperationalCost
from app.engine.octant_rewards.operational_cost.op_cost_percent import OpCostPercent
from app.engine.octant_rewards.ppf.calculator import PPFCalculatorPercent, PPFCalculator
from app.engine.octant_rewards.ppf.calculator import (
PPFCalculatorFromRewards,
PPFCalculator,
)
from app.engine.octant_rewards.total_and_individual import TotalAndAllIndividualRewards
from app.engine.octant_rewards.total_and_individual.tr_from_staking import (
from app.engine.octant_rewards.total_and_individual.tr_percent_calc import (
PercentTotalAndAllIndividualRewards,
)


class OctantRewardsDefaultValues:
OPERATIONAL_COST = Decimal("0.25")
PPF = Decimal("0.35")
COMMUNITY_FUND = Decimal("0.05")
TR_PERCENT = Decimal("0.7")
IRE_PERCENT = Decimal("0.35")
MATCHED_REWARDS_PERCENT = Decimal("0.35")


@dataclass
Expand All @@ -35,14 +41,17 @@ class OctantRewardsSettings:
)
total_and_all_individual_rewards: TotalAndAllIndividualRewards = field(
default_factory=lambda: PercentTotalAndAllIndividualRewards(
OctantRewardsDefaultValues.TR_PERCENT
IRE_PERCENT=OctantRewardsDefaultValues.IRE_PERCENT,
TR_PERCENT=OctantRewardsDefaultValues.TR_PERCENT,
)
)
matched_rewards: MatchedRewards = field(default_factory=MatchedRewardsWithPPF)

ppf: PPFCalculator = field(
default_factory=lambda: PPFCalculatorPercent(OctantRewardsDefaultValues.PPF)
matched_rewards: MatchedRewards = field(
default_factory=lambda: PercentageMatchedRewards(
OctantRewardsDefaultValues.MATCHED_REWARDS_PERCENT
)
)

ppf: PPFCalculator = field(default_factory=PPFCalculatorFromRewards)
community_fund: CommunityFundCalculator = field(
default_factory=lambda: CommunityFundPercent(
OctantRewardsDefaultValues.COMMUNITY_FUND
Expand Down
6 changes: 5 additions & 1 deletion backend/app/engine/octant_rewards/matched/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from decimal import Decimal


@dataclass
class MatchedRewardsPayload:
total_rewards: int = None
all_individual_rewards: int = None
patrons_rewards: int = None
ppf: int = None
staking_proceeds: int = None
ire_percent: Decimal = None
tr_percent: Decimal = None
locked_ratio: Decimal = None


@dataclass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from app.engine.octant_rewards.matched import MatchedRewards, MatchedRewardsPayload
from dataclasses import dataclass
from decimal import Decimal

from app.exceptions import InvalidMatchedRewardsStrategy


@dataclass
class PercentageMatchedRewards(MatchedRewards):
MATCHED_REWARDS_PERCENT: Decimal

def calculate_matched_rewards(self, payload: MatchedRewardsPayload) -> int:
if payload.locked_ratio > payload.tr_percent:
raise InvalidMatchedRewardsStrategy()

if payload.locked_ratio < payload.ire_percent:
return int(
self.MATCHED_REWARDS_PERCENT * payload.staking_proceeds
+ payload.patrons_rewards
)
elif payload.ire_percent <= payload.locked_ratio < payload.tr_percent:
return int(
(payload.tr_percent - payload.locked_ratio) * payload.staking_proceeds
+ payload.patrons_rewards
)
return payload.patrons_rewards
6 changes: 0 additions & 6 deletions backend/app/engine/octant_rewards/matched/with_ppf.py

This file was deleted.

6 changes: 5 additions & 1 deletion backend/app/engine/octant_rewards/ppf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from dataclasses import dataclass
from abc import abstractmethod, ABC
from decimal import Decimal


@dataclass
class PPFPayload:
eth_proceeds: int = None
individual_rewards_equilibrium: int
all_individual_rewards: int
locked_ratio: Decimal
ire_percent: Decimal


class PPFCalculator(ABC):
Expand Down
11 changes: 6 additions & 5 deletions backend/app/engine/octant_rewards/ppf/calculator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from app.engine.octant_rewards.ppf import PPFCalculator, PPFPayload
from decimal import Decimal
from dataclasses import dataclass


@dataclass
class PPFCalculatorPercent(PPFCalculator):
PPF_PERCENT: Decimal

class PPFCalculatorFromRewards(PPFCalculator):
def calculate_ppf(self, payload: PPFPayload) -> int:
return int(self.PPF_PERCENT * payload.eth_proceeds)
if payload.locked_ratio < payload.ire_percent:
return int(
payload.individual_rewards_equilibrium - payload.all_individual_rewards
)
return 0
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class TotalAndAllIndividualPayload:

@dataclass
class TotalAndAllIndividualRewards(ABC):
IRE_PERCENT: Decimal = None
TR_PERCENT: Decimal = None

@abstractmethod
def calculate_total_rewards(self, payload: TotalAndAllIndividualPayload) -> int:
pass
Expand All @@ -21,3 +24,8 @@ def calculate_all_individual_rewards(
self, payload: TotalAndAllIndividualPayload
) -> int:
pass

def calculate_individual_rewards_equilibrium(
self, payload: TotalAndAllIndividualPayload
):
return None
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from dataclasses import dataclass

from app.engine.octant_rewards.total_and_individual import (
TotalAndAllIndividualRewards,
TotalAndAllIndividualPayload,
)
from decimal import Decimal
from dataclasses import dataclass


@dataclass
class PercentTotalAndAllIndividualRewards(TotalAndAllIndividualRewards):
TR_PERCENT: Decimal

def calculate_total_rewards(self, payload: TotalAndAllIndividualPayload) -> int:
return int(payload.eth_proceeds * self.TR_PERCENT)
return int(self.TR_PERCENT * payload.eth_proceeds)

def calculate_all_individual_rewards(
self, payload: TotalAndAllIndividualPayload
) -> int:
return int(payload.eth_proceeds * payload.locked_ratio)

def calculate_individual_rewards_equilibrium(
self, payload: TotalAndAllIndividualPayload
) -> int:
return int(self.IRE_PERCENT * payload.eth_proceeds)
12 changes: 8 additions & 4 deletions backend/app/engine/user/budget/with_ppf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ def calculate_budget(self, payload: UserBudgetPayload) -> int:
payload.total_effective_deposit
)

calced_budget = int(
payload.all_individual_rewards * individual_share
+ individual_share
* (Decimal("0.5") * payload.ppf - payload.all_individual_rewards)
extra_individual_rewards = Decimal("0.5") * payload.ppf
full_individual_rewards = (
payload.all_individual_rewards + extra_individual_rewards
)

calced_budget = int(individual_share * full_individual_rewards)

# TODO all_individual_rewards should be named vanilla_individual_rewards

return calced_budget
10 changes: 10 additions & 0 deletions backend/app/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,13 @@ class InvalidMultisigAddress(OctantException):

def __init__(self):
super().__init__(self.description, self.code)


class InvalidMatchedRewardsStrategy(OctantException):
code = 500
description = (
"Can't calculate matched rewards when locked ratio is greater than TR percent"
)

def __init__(self):
super().__init__(self.description, self.code)
2 changes: 1 addition & 1 deletion backend/app/infrastructure/routes/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get(self):
),
"ppf": fields.String(
required=False,
description="PPF for the given epoch. It's calculated from staking proceeds directly. Includes individualRewards.",
description="PPF for the given epoch. It's calculated based on substracting Vanillia Individual Rewards from Individual Rewards Equilibrium.",
),
"communityFund": fields.String(
required=False,
Expand Down
10 changes: 9 additions & 1 deletion backend/app/modules/octant_rewards/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,17 @@ def calculate_rewards(
all_individual_rewards = rewards_calculator.calculate_all_individual_rewards(
rewards_payload
)
individual_rewards_equilibrium = (
rewards_calculator.calculate_individual_rewards_equilibrium(rewards_payload)
)

ppf_calculator = octant_rewards_settings.ppf
ppf_payload = PPFPayload(eth_proceeds)
ppf_payload = PPFPayload(
individual_rewards_equilibrium,
all_individual_rewards,
locked_ratio,
rewards_calculator.IRE_PERCENT,
)
ppf_value = ppf_calculator.calculate_ppf(ppf_payload)

cf_calculator = octant_rewards_settings.community_fund
Expand Down
10 changes: 4 additions & 6 deletions backend/app/modules/octant_rewards/service/pending.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from app.modules.common.leverage import calculate_leverage
from app.modules.dto import OctantRewardsDTO
from app.pydantic import Model
from app.engine.octant_rewards.ppf import PPFPayload


@runtime_checkable
Expand Down Expand Up @@ -42,17 +41,16 @@ def get_matched_rewards(self, context: Context) -> int:
)
patrons_rewards = self.patrons_mode.get_patrons_rewards(context)
matched_rewards_settings = context.epoch_settings.octant_rewards.matched_rewards
ppf_rewards_settings = context.epoch_settings.octant_rewards.ppf
ppf_value = ppf_rewards_settings.calculate_ppf(
PPFPayload(int(pending_snapshot.eth_proceeds))
)

return matched_rewards_settings.calculate_matched_rewards(
MatchedRewardsPayload(
total_rewards=int(pending_snapshot.total_rewards),
all_individual_rewards=int(pending_snapshot.all_individual_rewards),
patrons_rewards=patrons_rewards,
ppf=ppf_value,
staking_proceeds=int(pending_snapshot.eth_proceeds),
locked_ratio=Decimal(pending_snapshot.locked_ratio),
ire_percent=context.epoch_settings.octant_rewards.total_and_all_individual_rewards.IRE_PERCENT,
tr_percent=context.epoch_settings.octant_rewards.total_and_all_individual_rewards.TR_PERCENT,
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Update PPF after invalid calculations for Epoch3
Revision ID: 8e72b01c43a7
Revises: fc7a01c20be5
Create Date: 2024-04-19 00:48:12.133033
"""
from alembic import op
from decimal import Decimal

revision = "8e72b01c43a7"
down_revision = "fc7a01c20be5"
branch_labels = None
depends_on = None

individual_rewards = Decimal(
"146655862334541166188"
) # Got directly from the database for Pending Snapshot in Epoch3
staking_proceeds = Decimal(
"959848624830407629247"
) # Got directly from the database for Pending Snapshot in Epoch3
ppf = int(staking_proceeds * Decimal("0.35") - individual_rewards)


def upgrade():
query = f"UPDATE pending_epoch_snapshots SET ppf = '{ppf}' WHERE epoch = 3 AND all_individual_rewards = '{individual_rewards}' AND eth_proceeds = '{staking_proceeds}';"
op.execute(query)


def downgrade():
old_ppf = "335947018690642670236"

query = f"UPDATE pending_epoch_snapshots SET ppf = '{old_ppf}' WHERE epoch = 3 AND all_individual_rewards = '{individual_rewards}' AND eth_proceeds = '{staking_proceeds}' AND ppf = '{ppf}';"

print(query, flush=True)

op.execute(query)
2 changes: 1 addition & 1 deletion backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
USER3_BUDGET,
USER_MOCKED_BUDGET,
COMMUNITY_FUND,
PPF,
MOCKED_EPOCH_NO_AFTER_OVERHAUL,
MATCHED_REWARDS_AFTER_OVERHAUL,
NO_PATRONS_REWARDS,
Expand All @@ -59,6 +58,7 @@
MULTISIG_MOCKED_HASH,
MULTISIG_MOCKED_SAFE_HASH,
MULTISIG_ADDRESS,
PPF,
)
from tests.helpers.context import get_context
from tests.helpers.gql_client import MockGQLClient
Expand Down
43 changes: 42 additions & 1 deletion backend/tests/engine/octant_rewards/test_matched_rewards.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
from decimal import Decimal

import pytest

from app.engine.octant_rewards.matched.preliminary import PreliminaryMatchedRewards
from app.engine.octant_rewards.matched import MatchedRewardsPayload
from tests.helpers.constants import TOTAL_REWARDS, ALL_INDIVIDUAL_REWARDS
from app.engine.octant_rewards import (
PercentageMatchedRewards,
OctantRewardsDefaultValues,
)
from app.exceptions import InvalidMatchedRewardsStrategy
from tests.helpers.constants import (
TOTAL_REWARDS,
ALL_INDIVIDUAL_REWARDS,
ETH_PROCEEDS,
USER2_BUDGET,
)
from tests.modules.octant_rewards.helpers.overhaul_formulas import (
IRE_PERCENT,
TR_PERCENT,
)


def test_preliminary_matched_rewards():
Expand All @@ -13,3 +31,26 @@ def test_preliminary_matched_rewards():
result = uut.calculate_matched_rewards(payload)

assert result == 220115925184490486394


def test_matched_rewards_when_locked_ratio_greater_than_tr_percent():
payload = MatchedRewardsPayload(
staking_proceeds=ETH_PROCEEDS,
patrons_rewards=USER2_BUDGET,
locked_ratio=TR_PERCENT + Decimal("0.01"),
ire_percent=IRE_PERCENT,
tr_percent=TR_PERCENT,
)

uut = PercentageMatchedRewards(
MATCHED_REWARDS_PERCENT=OctantRewardsDefaultValues.MATCHED_REWARDS_PERCENT
)

with pytest.raises(InvalidMatchedRewardsStrategy) as exc:
uut.calculate_matched_rewards(payload)

assert exc.value.status_code == 500
assert (
exc.value.description
== "Can't calculate matched rewards when locked ratio is greater than TR percent"
)
Loading

0 comments on commit 019fea7

Please sign in to comment.