Skip to content

Commit

Permalink
Merge branch 'leon/OCT-1289/simulate-finalized-snapshot' into 'develop'
Browse files Browse the repository at this point in the history
OCT-1289: simulate finalized snapshot

See merge request golemfoundation/governance/octant!1096
  • Loading branch information
leoni-q committed Jan 26, 2024
2 parents cf9db9f + 5568767 commit c9242b8
Show file tree
Hide file tree
Showing 28 changed files with 552 additions and 81 deletions.
4 changes: 4 additions & 0 deletions backend/app/engine/projects/rewards/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class ProjectRewardDTO(JSONWizard):
allocated: int
matched: int

@property
def amount(self):
return self.allocated + self.matched

def __iter__(self):
yield self.address
yield self.allocated
Expand Down
4 changes: 2 additions & 2 deletions backend/app/engine/projects/rewards/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def calculate_project_rewards(
)

total_allocated_above_threshold = sum(
[allocated for _, allocated in allocated_by_addr if allocated >= threshold]
[allocated for _, allocated in allocated_by_addr if allocated > threshold]
)

project_rewards_sum = 0
Expand All @@ -55,7 +55,7 @@ def calculate_project_rewards(

for address, allocated in allocated_by_addr:
matched = 0
if allocated >= threshold:
if allocated > threshold:
matched = int(
Decimal(allocated)
/ Decimal(total_allocated_above_threshold)
Expand Down
29 changes: 26 additions & 3 deletions backend/app/infrastructure/database/allocations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
from eth_utils import to_checksum_address
from sqlalchemy import func
from sqlalchemy.orm import Query
from typing_extensions import deprecated

from app.exceptions import UserNotFound
from app.extensions import db
from app.infrastructure.database.models import Allocation, User, AllocationRequest
from app.infrastructure.database.user import get_by_address
from app.legacy.core.common import AccountFunds
from app.modules.dto import AllocationDTO, AccountFundsDTO


@deprecated("Use `get_all` function")
def get_all_by_epoch(epoch: int, with_deleted=False) -> List[Allocation]:
query: Query = Allocation.query.filter_by(epoch=epoch)

Expand All @@ -22,6 +24,24 @@ def get_all_by_epoch(epoch: int, with_deleted=False) -> List[Allocation]:
return query.all()


def get_all(epoch: int, with_deleted=False) -> List[AllocationDTO]:
query: Query = Allocation.query.filter_by(epoch=epoch)

if not with_deleted:
query = query.filter(Allocation.deleted_at.is_(None))

allocations = query.all()

return [
AllocationDTO(
amount=int(a.amount),
proposal_address=a.proposal_address,
user_address=a.user.address,
)
for a in allocations
]


def get_user_allocations_history(
user_address: str, from_datetime: datetime, limit: int
) -> List[Allocation]:
Expand Down Expand Up @@ -103,7 +123,7 @@ def get_users_with_allocations(epoch_num: int) -> List[str]:
return [u.address for u in users]


def get_alloc_sum_by_epoch_and_user_address(epoch: int) -> List[AccountFunds]:
def get_alloc_sum_by_epoch_and_user_address(epoch: int) -> List[AccountFundsDTO]:
allocations = (
db.session.query(User, Allocation)
.join(User, User.id == Allocation.user_id)
Expand All @@ -118,7 +138,10 @@ def get_alloc_sum_by_epoch_and_user_address(epoch: int) -> List[AccountFunds]:
allocations_by_user[user.address] += int(allocation.amount)

sorted_allocations = sorted(
[AccountFunds(address=i[0], amount=i[1]) for i in allocations_by_user.items()],
[
AccountFundsDTO(address=i[0], amount=i[1])
for i in allocations_by_user.items()
],
key=lambda item: item.address,
)

Expand Down
44 changes: 43 additions & 1 deletion backend/app/infrastructure/routes/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from flask import current_app as app
from flask_restx import Namespace, fields

from app.legacy.controllers import snapshots
from app.extensions import api
from app.infrastructure import OctantResource
from app.legacy.controllers import snapshots
from app.modules.snapshots.finalized.controller import simulate_finalized_epoch_snapshot
from app.modules.snapshots.pending.controller import create_pending_epoch_snapshot

ns = Namespace("snapshots", description="Database snapshots")
Expand All @@ -28,6 +29,36 @@
},
)

project_reward_model = api.model(
"ProjectRewardModel",
{
"address": fields.String(),
"allocated": fields.String(),
"matched": fields.String(),
},
)

account_funds_model = api.model(
"UserRewardModel",
{
"address": fields.String(),
"amount": fields.String(),
},
)

finalized_snapshot_model = api.model(
"FinalizedSnapshotModel",
{
"patronsRewards": fields.String(),
"matchedRewards": fields.String(),
"projectsRewards": fields.List(fields.Nested(project_reward_model)),
"userRewards": fields.List(fields.Nested(account_funds_model)),
"totalWithdrawals": fields.String(),
"leftover": fields.String(),
"merkleRoot": fields.String(),
},
)


@ns.route("/pending")
@ns.doc(
Expand Down Expand Up @@ -88,3 +119,14 @@ def get(self, epoch: int):
app.logger.debug(f"Epoch {epoch} status: {status}")

return status.to_dict()


@ns.route("/finalized/simulate")
@ns.doc(description="Simulates the finalized snapshot")
@ns.response(200, "Snapshot simulated successfully")
class SimulateFinalizedSnapshot(OctantResource):
@ns.marshal_with(finalized_snapshot_model)
def get(self):
app.logger.debug("Simulating finalized snapshot")
response = simulate_finalized_epoch_snapshot()
return response.to_dict()
4 changes: 2 additions & 2 deletions backend/app/legacy/core/proposals.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ def get_finalized_rewards(
threshold = get_proposal_allocation_threshold(epoch)

total_allocated_above_threshold = sum(
[allocated for _, allocated in projects if allocated >= threshold]
[allocated for _, allocated in projects if allocated > threshold]
)

proposal_rewards_sum = 0
proposal_rewards: List[AccountFunds] = []

for address, allocated in projects:
if allocated >= threshold:
if allocated > threshold:
matched = int(
Decimal(allocated)
/ Decimal(total_allocated_above_threshold)
Expand Down
35 changes: 35 additions & 0 deletions backend/app/modules/common/merkle_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import List

from multiproof import StandardMerkleTree

from app.exceptions import MissingAddress
from app.infrastructure import database
from app.modules.dto import AccountFundsDTO

LEAF_ENCODING: List[str] = ["address", "uint256"]


def get_proof_by_address_and_epoch(address: str, epoch: int) -> List[str]:
merkle_tree = get_merkle_tree_for_epoch(epoch)
return get_proof(merkle_tree, address)


def get_merkle_tree_for_epoch(epoch: int) -> StandardMerkleTree:
leaves = [
AccountFundsDTO(r.address, int(r.amount))
for r in database.rewards.get_by_epoch(epoch)
]
return build_merkle_tree(leaves)


def build_merkle_tree(leaves: List[AccountFundsDTO]) -> StandardMerkleTree:
return StandardMerkleTree.of(
[[leaf.address, leaf.amount] for leaf in leaves], LEAF_ENCODING
)


def get_proof(mt: StandardMerkleTree, address: str) -> List[str]:
for i, leaf in enumerate(mt.values):
if leaf.value[0] == address:
return mt.get_proof(i)
raise MissingAddress(address)
20 changes: 20 additions & 0 deletions backend/app/modules/common/project_rewards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List

from app.engine.projects import ProjectSettings
from app.engine.projects.rewards import ProjectRewardDTO, ProjectRewardsPayload
from app.modules.dto import AllocationDTO


def get_projects_rewards(
project_settings: ProjectSettings,
allocations: List[AllocationDTO],
all_projects: List[str],
matched_rewards: int,
) -> (List[ProjectRewardDTO], int, int):
return project_settings.rewards.calculate_project_rewards(
ProjectRewardsPayload(
allocations=allocations,
matched_rewards=matched_rewards,
projects=all_projects,
)
)
25 changes: 23 additions & 2 deletions backend/app/modules/dto.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
from typing import Optional, List

from dataclass_wizard import JSONWizard

from app.engine.projects.rewards import AllocationPayload
from app.engine.projects.rewards import AllocationPayload, ProjectRewardDTO


@dataclass(frozen=True)
class AccountFundsDTO(JSONWizard):
address: str
amount: int

def __iter__(self):
yield self.address
yield self.amount


@dataclass(frozen=True)
class FinalizedSnapshotDTO(JSONWizard):
patrons_rewards: int
matched_rewards: int
projects_rewards: List[ProjectRewardDTO]
user_rewards: List[AccountFundsDTO]
total_withdrawals: int
leftover: int
merkle_root: str


@dataclass
Expand Down
29 changes: 20 additions & 9 deletions backend/app/modules/modules_factory/finalizing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
UserEffectiveDeposits,
TotalEffectiveDeposits,
Leverage,
FinalizedSnapshots,
)
from app.modules.octant_rewards.service.pending import PendingOctantRewards
from app.modules.snapshots.finalized.service.simulated import (
SimulatedFinalizedSnapshots,
)
from app.modules.user.allocations.service.saved import SavedUserAllocations
from app.modules.user.budgets.service.saved import SavedUserBudgets
from app.modules.user.deposits.service.saved import SavedUserDeposits
from app.modules.user.patron_mode.service.events_based import EventsBasedUserPatronMode
from app.modules.user.rewards.service.saved import SavedUserRewards
from app.modules.user.rewards.service.calculated import CalculatedUserRewards


class FinalizingOctantRewards(OctantRewards, Leverage, Protocol):
Expand All @@ -33,22 +37,29 @@ class FinalizingServices:
user_allocations_service: DonorsAddresses
user_patron_mode_service: UserPatronMode
user_rewards_service: UserRewards
finalized_snapshots_service: FinalizedSnapshots

@staticmethod
def create() -> "FinalizingServices":
events_based_patron_mode = EventsBasedUserPatronMode()
saved_user_allocations = SavedUserAllocations()
octant_rewards = PendingOctantRewards(patrons_mode=events_based_patron_mode)
user_rewards = CalculatedUserRewards(
user_budgets=SavedUserBudgets(),
patrons_mode=events_based_patron_mode,
allocations=saved_user_allocations,
)
finalized_snapshots_service = SimulatedFinalizedSnapshots(
octant_rewards=octant_rewards,
user_rewards=user_rewards,
patrons_mode=events_based_patron_mode,
)

return FinalizingServices(
user_deposits_service=SavedUserDeposits(),
octant_rewards_service=PendingOctantRewards(
patrons_mode=events_based_patron_mode
),
octant_rewards_service=octant_rewards,
user_allocations_service=saved_user_allocations,
user_patron_mode_service=events_based_patron_mode,
user_rewards_service=SavedUserRewards(
user_budgets=SavedUserBudgets(),
patrons_mode=events_based_patron_mode,
allocations=saved_user_allocations,
),
user_rewards_service=user_rewards,
finalized_snapshots_service=finalized_snapshots_service,
)
24 changes: 18 additions & 6 deletions backend/app/modules/modules_factory/pending.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
TotalEffectiveDeposits,
Leverage,
SimulateAllocation,
FinalizedSnapshots,
)
from app.modules.octant_rewards.service.pending import PendingOctantRewards
from app.modules.snapshots.finalized.service.simulated import (
SimulatedFinalizedSnapshots,
)
from app.modules.user.allocations.service.pending import PendingUserAllocations
from app.modules.user.budgets.service.saved import SavedUserBudgets
from app.modules.user.deposits.service.saved import SavedUserDeposits
from app.modules.user.patron_mode.service.events_based import EventsBasedUserPatronMode
from app.modules.user.rewards.service.saved import SavedUserRewards
from app.modules.user.rewards.service.calculated import CalculatedUserRewards


class PendingOctantRewardsService(OctantRewards, Leverage, Protocol):
Expand All @@ -38,21 +42,29 @@ class PendingServices:
user_allocations_service: PendingUserAllocationsProtocol
user_patron_mode_service: UserPatronMode
user_rewards_service: UserRewards
finalized_snapshots_service: FinalizedSnapshots

@staticmethod
def create() -> "PendingServices":
events_based_patron_mode = EventsBasedUserPatronMode()
octant_rewards = PendingOctantRewards(patrons_mode=events_based_patron_mode)
saved_user_allocations = PendingUserAllocations(octant_rewards=octant_rewards)
user_rewards = CalculatedUserRewards(
user_budgets=SavedUserBudgets(),
patrons_mode=events_based_patron_mode,
allocations=saved_user_allocations,
)
finalized_snapshots_service = SimulatedFinalizedSnapshots(
octant_rewards=octant_rewards,
user_rewards=user_rewards,
patrons_mode=events_based_patron_mode,
)

return PendingServices(
user_deposits_service=SavedUserDeposits(),
octant_rewards_service=octant_rewards,
user_allocations_service=saved_user_allocations,
user_patron_mode_service=events_based_patron_mode,
user_rewards_service=SavedUserRewards(
user_budgets=SavedUserBudgets(),
patrons_mode=events_based_patron_mode,
allocations=saved_user_allocations,
),
finalized_snapshots_service=finalized_snapshots_service,
user_rewards_service=user_rewards,
)
Loading

0 comments on commit c9242b8

Please sign in to comment.