Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate /rewards routes #604

Merged
merged 16 commits into from
Jan 30, 2025
Prev Previous commit
Next Next commit
Adds allocation simulation
adam-gf committed Jan 29, 2025
commit 3d966f7d31b43ecc7816478e769eaf253dbae327
39 changes: 30 additions & 9 deletions backend/v2/allocations/router.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from typing import Annotated
from fastapi import APIRouter, Query
from v2.matched_rewards.dependencies import GetMatchedRewardsEstimator
from v2.projects.dependencies import GetProjectsContracts
from v2.uniqueness_quotients.dependencies import GetUQScoreGetter
from v2.allocations.services import simulate_allocation
from v2.epochs.dependencies import GetOpenAllocationWindowEpochNumber
from v2.allocations.repositories import (
get_all_allocations_for_epoch,
get_allocations_by_user,
@@ -15,6 +20,8 @@
EpochAllocationsResponseV1,
EpochDonorsResponseV1,
ProjectAllocationV1,
SimulateAllocationPayloadV1,
SimulateAllocationResponseV1,
UserAllocationNonceV1,
UserAllocationRequest,
UserAllocationRequestV1,
@@ -83,15 +90,29 @@ async def get_all_allocations_for_epoch_v1(
return EpochAllocationsResponseV1(allocations=donations)


# @api.post("/leverage/{user_address}")
# async def simulate_allocation_v1(
# user_address: Address, payload: SimulateAllocationPayloadV1
# ) -> None:
# """
# Simulates an allocation and get the expected leverage, threshold and matched rewards.
# """
# # TODO: implement
# pass
@api.post("/leverage/{user_address}")
async def simulate_allocation_v1(
session: GetSession,
projects_contracts: GetProjectsContracts,
matched_rewards_estimator: GetMatchedRewardsEstimator,
uq_score_getter: GetUQScoreGetter,
pending_epoch_number: GetOpenAllocationWindowEpochNumber,
# Request Parameters
user_address: Address,
payload: SimulateAllocationPayloadV1,
) -> SimulateAllocationResponseV1:
"""
Simulates an allocation and get the expected leverage, threshold and matched rewards.
"""
return await simulate_allocation(
session,
projects_contracts,
matched_rewards_estimator,
uq_score_getter,
pending_epoch_number,
user_address,
payload.allocations,
)


@api.get("/project/{project_address}/epoch/{epoch_number}")
10 changes: 5 additions & 5 deletions backend/v2/allocations/schemas.py
Original file line number Diff line number Diff line change
@@ -66,16 +66,16 @@ class UserAllocationsResponseV1(OctantModel):
is_manually_edited: bool | None


class SimulateAllocationPayloadV1(OctantModel):
allocations: list[AllocationRequestV1]


class ProjectMatchedRewardsV1(OctantModel):
address: Address
value: BigInteger


class SimulateAllocationPayloadV1(OctantModel):
allocations: list[AllocationRequestV1]


class SimulateAllocationResponseV1(OctantModel):
leverage: Decimal
threshold: int
threshold: BigInteger | None
matched: list[ProjectMatchedRewardsV1]
95 changes: 93 additions & 2 deletions backend/v2/allocations/services.py
Original file line number Diff line number Diff line change
@@ -2,15 +2,27 @@

from app import exceptions
from sqlalchemy.ext.asyncio import AsyncSession
from v2.core.types import Address
from v2.allocations.repositories import (
get_allocations_with_user_uqs,
soft_delete_user_allocations_by_epoch,
store_allocation_request,
)
from v2.allocations.schemas import AllocationWithUserUQScore, UserAllocationRequest
from v2.allocations.schemas import (
AllocationRequestV1,
AllocationWithUserUQScore,
ProjectMatchedRewardsV1,
SimulateAllocationResponseV1,
UserAllocationRequest,
)
from v2.allocations.validators import SignatureVerifier
from v2.matched_rewards.services import MatchedRewardsEstimator
from v2.project_rewards.capped_quadratic import cqf_simulate_leverage
from v2.project_rewards.capped_quadratic import (
MR_FUNDING_CAP_PERCENT,
capped_quadratic_funding,
cqf_calculate_individual_leverage,
cqf_simulate_leverage,
)
from v2.projects.contracts import ProjectsContracts
from v2.uniqueness_quotients.dependencies import UQScoreGetter
from v2.users.repositories import get_user_by_address
@@ -112,6 +124,85 @@ async def allocate(
return request.user_address


async def simulate_allocation(
# Component dependencies
session: AsyncSession,
projects_contracts: ProjectsContracts,
matched_rewards_estimator: MatchedRewardsEstimator,
uq_score_getter: UQScoreGetter,
# Arguments
epoch_number: int,
user_address: Address,
new_allocations: list[AllocationRequestV1],
) -> SimulateAllocationResponseV1:
"""
Simulate the allocation of the user and calculate the leverage and matched project rewards.
This just "ignores" the user's current allocations and calculates the leverage as if the user
had made the allocation.
"""

# Get or calculate UQ score of the user
user_uq_score = await uq_score_getter.get_or_calculate(
epoch_number=epoch_number,
user_address=user_address,
)
new_allocations_with_uq = [
AllocationWithUserUQScore(
project_address=a.project_address,
amount=a.amount,
user_address=user_address,
user_uq_score=user_uq_score,
)
for a in new_allocations
]

# Calculate leverage and matched project rewards
all_projects = await projects_contracts.get_project_addresses(epoch_number)
existing_allocations = await get_allocations_with_user_uqs(session, epoch_number)
matched_rewards = await matched_rewards_estimator.get()

allocations_without_user = [
a for a in existing_allocations if a.user_address != user_address
]

# Calculate capped quadratic funding before and after the user's allocation
# Leverage is more or less a difference between the two (before - after)
before_allocation = capped_quadratic_funding(
allocations_without_user,
matched_rewards,
all_projects,
MR_FUNDING_CAP_PERCENT,
)
after_allocation = capped_quadratic_funding(
allocations_without_user + new_allocations_with_uq,
matched_rewards,
all_projects,
MR_FUNDING_CAP_PERCENT,
)

leverage = cqf_calculate_individual_leverage(
new_allocations_amount=sum(a.amount for a in new_allocations_with_uq),
project_addresses=[a.project_address for a in new_allocations_with_uq],
before_allocation=before_allocation,
after_allocation=after_allocation,
)

return SimulateAllocationResponseV1(
leverage=leverage,
threshold=None,
matched=sorted(
[
ProjectMatchedRewardsV1(
address=p.address,
value=p.matched,
)
for p in after_allocation.project_fundings.values()
],
key=lambda x: x.address,
),
)


async def simulate_leverage(
# Component dependencies
session: AsyncSession,