Skip to content

Commit

Permalink
OCT-1837 & OCT-1838: Upgrade metrics pie chart for unused MR (#383)
Browse files Browse the repository at this point in the history
## Description

## Definition of Done

1. [ ] Acceptance criteria are met.
2. [ ] PR is manually tested before the merge by developer(s).
    - [ ] Happy path is manually checked.
3. [ ] PR is manually tested by QA when their assistance is required
(1).
- [ ] Octant Areas & Test Cases are checked for impact and updated if
required (2).
4. [ ] Unit tests are added unless there is a reason to omit them.
5. [ ] Automated tests are added when required.
6. [ ] The code is merged.
7. [ ] Tech documentation is added / updated, reviewed and approved
(including mandatory approval by a code owner, should such exist for
changed files).
    - [ ] BE: Swagger documentation is updated.
8. [ ] When required by QA:
    - [ ] Deployed to the relevant environment.
    - [ ] Passed system tests.

---

(1) Developer(s) in coordination with QA decide whether it's required.
For small tickets introducing small changes QA assistance is most
probably not required.

(2) [Octant Areas & Test
Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc).

---------

Co-authored-by: Andrzej Ziółek <[email protected]>
  • Loading branch information
kgarbacinski and aziolek authored Sep 2, 2024
1 parent 077ed90 commit 44a889f
Show file tree
Hide file tree
Showing 26 changed files with 288 additions and 50 deletions.
3 changes: 3 additions & 0 deletions backend/app/engine/octant_rewards/leftover/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ class Leftover(ABC):
@abstractmethod
def calculate_leftover(self, payload: LeftoverPayload) -> int:
pass

def extract_unused_matched_rewards(self, *args, **kwargs) -> int:
return 0
11 changes: 11 additions & 0 deletions backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ def calculate_leftover(self, payload: LeftoverPayload) -> int:
- payload.total_withdrawals
+ unused_matched_rewards
)

def extract_unused_matched_rewards(self, leftover, payload) -> int:
extra_individual_rewards = int(payload.ppf / 2)
return (
leftover
- payload.staking_proceeds
+ payload.operational_cost
+ payload.community_fund
+ extra_individual_rewards
+ payload.total_withdrawals
)
17 changes: 16 additions & 1 deletion backend/app/engine/projects/rewards/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from collections import namedtuple
from dataclasses import dataclass, field
from typing import List, Optional
from typing import List, Optional, Dict

from dataclass_wizard import JSONWizard

Expand Down Expand Up @@ -41,6 +42,11 @@ class ProjectRewardsResult:
threshold: Optional[int] = None


AllocationsBelowThreshold = namedtuple(
"AllocationsBelowThreshold", ["below_threshold", "total"]
)


@dataclass
class ProjectRewards(ABC):
projects_allocations: ProjectAllocations = field(init=False)
Expand All @@ -54,3 +60,12 @@ def calculate_project_rewards(

def calculate_threshold(self, total_allocated: int, projects: List[str]) -> None:
return None

def get_total_allocations_below_threshold(
self, allocations: Dict[str, List[AllocationItem]]
) -> AllocationsBelowThreshold:
allocations_sum = sum(
sum(int(item.amount) for item in project_allocations)
for project_allocations in allocations.values()
)
return AllocationsBelowThreshold(0, allocations_sum)
12 changes: 9 additions & 3 deletions backend/app/engine/projects/rewards/allocations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,24 @@ def _calc_allocations(
) -> Union[int, Decimal]:
...

def group_allocations_by_projects(
def segregate_allocations(
self, payload: ProjectAllocationsPayload
) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]):
result_allocations = []
) -> Dict[str, List]:
grouped_allocations = {
key: list(group)
for key, group in groupby(
sorted(payload.allocations, key=lambda a: a.project_address),
key=lambda a: a.project_address,
)
}
return grouped_allocations

def group_allocations_by_projects(
self, payload: ProjectAllocationsPayload
) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]):
grouped_allocations = self.segregate_allocations(payload)

result_allocations = []
total_plain_qf = 0
for project_address, project_allocations in grouped_allocations.items():
project_allocations = self._calc_allocations(project_allocations)
Expand Down
13 changes: 11 additions & 2 deletions backend/app/engine/projects/rewards/preliminary.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
from dataclasses import field, dataclass
from decimal import Decimal
from typing import List
from typing import List, Dict

from app.engine.projects.rewards import (
ProjectRewardsPayload,
ProjectRewardsResult,
ProjectRewards,
ProjectRewardDTO,
AllocationsBelowThreshold,
)
from app.engine.projects.rewards.allocations import (
ProjectAllocations,
ProjectAllocationsPayload,
AllocationItem,
)
from app.engine.projects.rewards.allocations.preliminary import (
PreliminaryProjectAllocations,
)
from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage
from app.engine.projects.rewards.threshold import (
ProjectThreshold,
ProjectThresholdPayload,
)
from app.engine.projects.rewards.threshold.preliminary import (
PreliminaryProjectThreshold,
)
from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage


@dataclass
Expand All @@ -42,6 +44,13 @@ def calculate_threshold(self, total_allocated: int, projects: List[str]) -> int:
)
)

def get_total_allocations_below_threshold(
self, allocations: Dict[str, List[AllocationItem]]
) -> AllocationsBelowThreshold:
return self.projects_threshold.get_total_allocations_below_threshold(
allocations
)

def calculate_project_rewards(
self, payload: ProjectRewardsPayload
) -> ProjectRewardsResult:
Expand Down
10 changes: 10 additions & 0 deletions backend/app/engine/projects/rewards/threshold/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Dict

from app.engine.projects.rewards import AllocationsBelowThreshold
from app.engine.projects.rewards import AllocationItem


@dataclass
Expand All @@ -13,3 +17,9 @@ class ProjectThreshold(ABC):
@abstractmethod
def calculate_threshold(self, payload: ProjectThresholdPayload) -> int:
pass

@abstractmethod
def get_total_allocations_below_threshold(
self, allocations: Dict[str, List[AllocationItem]]
) -> AllocationsBelowThreshold:
pass
24 changes: 24 additions & 0 deletions backend/app/engine/projects/rewards/threshold/preliminary.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from dataclasses import dataclass
from typing import List, Dict

from app.engine.projects.rewards import AllocationsBelowThreshold
from app.engine.projects.rewards.threshold import (
ProjectThreshold,
ProjectThresholdPayload,
)
from app.engine.projects.rewards import AllocationItem


@dataclass
Expand All @@ -19,3 +22,24 @@ def calculate_threshold(self, payload: ProjectThresholdPayload) -> int:
if payload.projects_count
else 0
)

def get_total_allocations_below_threshold(
self, allocations: Dict[str, List[AllocationItem]]
) -> AllocationsBelowThreshold:
summed_allocations = {
project: sum(map(lambda value: int(value.amount), values))
for project, values in allocations.items()
}
total_allocations = sum(summed_allocations.values())
no_projects = len(allocations.keys())

threshold = self.calculate_threshold(
ProjectThresholdPayload(total_allocations, no_projects)
)

allocations_below_threshold = 0
for allocations_sum_for_project in summed_allocations.values():
if allocations_sum_for_project < threshold:
allocations_below_threshold += allocations_sum_for_project

return AllocationsBelowThreshold(allocations_below_threshold, total_allocations)
4 changes: 4 additions & 0 deletions backend/app/infrastructure/routes/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ def get(self):
required=False,
description="Community fund for the given epoch. It's calculated from staking proceeds directly.",
),
"donatedToProjects": fields.String(
required=False,
description="The amount of funds donated to projects. Includes MR and allocations.",
),
},
)

Expand Down
2 changes: 2 additions & 0 deletions backend/app/modules/dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class OctantRewardsDTO(JSONWizard):
# Data available starting from Epoch 3
ppf: Optional[int] = None
community_fund: Optional[int] = None
# Added after moving metrics from FE to BE
donated_to_projects: Optional[int] = None


@dataclass(frozen=True)
Expand Down
34 changes: 34 additions & 0 deletions backend/app/modules/octant_rewards/general/service/finalized.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from app.infrastructure import database
from app.modules.dto import OctantRewardsDTO
from app.pydantic import Model
from app.engine.octant_rewards.leftover import LeftoverPayload
from app.engine.projects.rewards.allocations import ProjectAllocationsPayload


class FinalizedOctantRewards(Model):
Expand All @@ -15,6 +17,37 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO:
context.epoch_details.epoch_num
)

leftover_payload = LeftoverPayload(
staking_proceeds=int(pending_snapshot.eth_proceeds),
operational_cost=int(pending_snapshot.operational_cost),
community_fund=pending_snapshot.validated_community_fund,
ppf=pending_snapshot.validated_ppf,
total_withdrawals=int(finalized_snapshot.total_withdrawals),
)

unused_matched_rewards = context.epoch_settings.octant_rewards.leftover.extract_unused_matched_rewards(
int(finalized_snapshot.leftover), leftover_payload
)

allocations_for_epoch = database.allocations.get_all_by_epoch(
context.epoch_details.epoch_num
)
grouped_allocations = context.epoch_settings.project.rewards.projects_allocations.segregate_allocations(
ProjectAllocationsPayload(allocations=allocations_for_epoch)
)
allocations_result = context.epoch_settings.project.rewards.get_total_allocations_below_threshold(
grouped_allocations
)
allocations_sum = allocations_result.total
allocations_below_threshold = allocations_result.below_threshold

donated_to_projects = (
int(finalized_snapshot.matched_rewards)
- unused_matched_rewards
+ allocations_sum
- allocations_below_threshold
)

return OctantRewardsDTO(
staking_proceeds=int(pending_snapshot.eth_proceeds),
locked_ratio=Decimal(pending_snapshot.locked_ratio),
Expand All @@ -28,6 +61,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO:
total_withdrawals=int(finalized_snapshot.total_withdrawals),
ppf=pending_snapshot.validated_ppf,
community_fund=pending_snapshot.validated_community_fund,
donated_to_projects=donated_to_projects,
)

def get_leverage(self, context: Context) -> float:
Expand Down
35 changes: 25 additions & 10 deletions backend/app/modules/octant_rewards/general/service/pending.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class ProjectRewards(Protocol):
def get_finalized_project_rewards(
self,
context: Context,
allocations: list[AllocationDTO],
all_projects: list[str],
allocations: List[AllocationDTO],
all_projects: List[str],
matched_rewards: int,
) -> FinalizedProjectRewards:
...
Expand All @@ -51,6 +51,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO:
context.epoch_details.epoch_num
)
matched_rewards = self.octant_matched_rewards.get_matched_rewards(context)
project_rewards = self._get_project_rewards(context, matched_rewards)

return OctantRewardsDTO(
staking_proceeds=int(pending_snapshot.eth_proceeds),
Expand All @@ -63,7 +64,10 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO:
ppf=pending_snapshot.validated_ppf,
matched_rewards=matched_rewards,
patrons_rewards=self.patrons_mode.get_patrons_rewards(context),
leftover=self.get_leftover(context, pending_snapshot, matched_rewards),
leftover=self._get_leftover(
context, pending_snapshot, matched_rewards, project_rewards
),
donated_to_projects=self._get_donated_to_projects(project_rewards),
)

def get_matched_rewards(self, context: Context) -> int:
Expand All @@ -79,19 +83,14 @@ def get_leverage(self, context: Context) -> float:
matched_rewards, allocations_sum
)

def get_leftover(
def _get_leftover(
self,
context: Context,
pending_snapshot: PendingEpochSnapshot,
matched_rewards: int,
project_rewards: FinalizedProjectRewards,
) -> int:
allocations = database.allocations.get_all_with_uqs(
context.epoch_details.epoch_num
)
_, user_rewards = self.user_rewards.get_claimed_rewards(context)
project_rewards = self.project_rewards.get_finalized_project_rewards(
context, allocations, context.projects_details.projects, matched_rewards
)

return context.epoch_settings.octant_rewards.leftover.calculate_leftover(
LeftoverPayload(
Expand All @@ -106,3 +105,19 @@ def get_leftover(
used_matched_rewards=sum(r.matched for r in project_rewards.rewards),
)
)

def _get_donated_to_projects(self, project_rewards: FinalizedProjectRewards) -> int:
total_user_donations_with_used_matched_rewards = sum(
r.amount for r in project_rewards.rewards
)

return total_user_donations_with_used_matched_rewards

def _get_project_rewards(self, context: Context, matched_rewards: int):
allocations = database.allocations.get_all_with_uqs(
context.epoch_details.epoch_num
)
project_rewards = self.project_rewards.get_finalized_project_rewards(
context, allocations, context.projects_details.projects, matched_rewards
)
return project_rewards
6 changes: 4 additions & 2 deletions backend/app/modules/projects/rewards/service/finalizing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List

from app.context.manager import Context
from app.modules.common.project_rewards import get_projects_rewards, AllocationsPayload
from app.modules.dto import AllocationDTO, ProjectAccountFundsDTO
Expand All @@ -9,8 +11,8 @@ class FinalizingProjectRewards(Model):
def get_finalized_project_rewards(
self,
context: Context,
allocations: list[AllocationDTO],
all_projects: list[str],
allocations: List[AllocationDTO],
all_projects: List[str],
matched_rewards: int,
) -> FinalizedProjectRewards:
allocations_payload = AllocationsPayload(
Expand Down
Loading

0 comments on commit 44a889f

Please sign in to comment.