Skip to content

Commit

Permalink
Remove constants from payout datum (#418)
Browse files Browse the repository at this point in the history
This PR makes the payout datum independent of constants.

This will make it easier to pass a config object through the code, as it
will not be needed to pass to the creation of transfers from a payout
datum.

- This PR adds an explicit separation between reward and buffer
accounting target. This way we do not need to check for the bonding pool
when creating transfers. This change requires adding a new column,
`"buffer_accounting_target"`, to the payment dataframe.
- The type of the service fee field is changed to `Fraction` from
`bool`. This makes it possible to avoid passing the service fee factor
to several functions related to creating transfers. This requires a
change in meaning of the `"service_fee"` column.
- The reward token address (of COW, which might depend on the network)
is explicitly part of the payment datum. This requires adding a column
`"reward_target_address"`, to the payment dataframe.

Tests are adapted accordingly. I have not yet tested the effect on
payments. In principle, the behavior should not change.
  • Loading branch information
fhenneke authored Nov 11, 2024
1 parent 4aac141 commit 3645d92
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 54 deletions.
75 changes: 51 additions & 24 deletions src/fetch/payouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from fractions import Fraction
from typing import Callable

import numpy as np
import pandas
from dune_client.types import Address
from pandas import DataFrame, Series
Expand Down Expand Up @@ -45,8 +46,13 @@
}
REWARD_TARGET_COLUMNS = {"solver", "reward_target", "pool_address"}
SERVICE_FEE_COLUMNS = {"solver", "service_fee"}
ADDITIONAL_PAYMENT_COLUMNS = {"buffer_accounting_target", "reward_token_address"}

COMPLETE_COLUMNS = PAYMENT_COLUMNS.union(SLIPPAGE_COLUMNS).union(REWARD_TARGET_COLUMNS)
COMPLETE_COLUMNS = (
PAYMENT_COLUMNS.union(SLIPPAGE_COLUMNS)
.union(REWARD_TARGET_COLUMNS)
.union(ADDITIONAL_PAYMENT_COLUMNS)
)
NUMERICAL_COLUMNS = [
"primary_reward_eth",
"primary_reward_cow",
Expand All @@ -73,26 +79,28 @@ def __init__( # pylint: disable=too-many-arguments
self,
solver: Address,
solver_name: str,
reward_target: Address,
bonding_pool: Address,
reward_target: Address, # recipient address of rewards
buffer_accounting_target: Address, # recipient address of net buffer changes
primary_reward_eth: int,
slippage_eth: int,
primary_reward_cow: int,
quote_reward_cow: int,
service_fee: bool,
service_fee: Fraction,
reward_token_address: Address,
):

assert quote_reward_cow >= 0, "invalid quote_reward_cow"

self.solver = solver
self.solver_name = solver_name
self.reward_target = reward_target
self.bonding_pool = bonding_pool
self.buffer_accounting_target = buffer_accounting_target
self.slippage_eth = slippage_eth
self.primary_reward_eth = primary_reward_eth
self.primary_reward_cow = primary_reward_cow
self.quote_reward_cow = quote_reward_cow
self.service_fee = service_fee
self.reward_token_address = reward_token_address

@classmethod
def from_series(cls, frame: Series) -> RewardAndPenaltyDatum:
Expand All @@ -104,21 +112,28 @@ def from_series(cls, frame: Series) -> RewardAndPenaltyDatum:
)
solver = frame["solver"]
reward_target = frame["reward_target"]
bonding_pool = frame["pool_address"]
if reward_target is None:
logging.warning(f"solver {solver} without reward_target. Using solver")
logging.warning(f"Solver {solver} without reward_target. Using solver")
reward_target = solver

buffer_accounting_target = frame["buffer_accounting_target"]
if buffer_accounting_target is None:
logging.warning(
f"Solver {solver} without buffer_accounting_target. Using solver"
)
buffer_accounting_target = solver

return cls(
solver=Address(solver),
solver_name=frame["solver_name"],
reward_target=Address(reward_target),
bonding_pool=Address(bonding_pool),
buffer_accounting_target=Address(buffer_accounting_target),
slippage_eth=slippage,
primary_reward_eth=int(frame["primary_reward_eth"]),
primary_reward_cow=int(frame["primary_reward_cow"]),
quote_reward_cow=int(frame["quote_reward_cow"]),
service_fee=bool(frame["service_fee"]),
service_fee=Fraction(frame["service_fee"]),
reward_token_address=Address(frame["reward_token_address"]),
)

def total_outgoing_eth(self) -> int:
Expand All @@ -136,7 +151,7 @@ def total_eth_reward(self) -> int:
def reward_scaling(self) -> Fraction:
"""Scaling factor for service fee
The reward is multiplied by this factor"""
return 1 - SERVICE_FEE_FACTOR * self.service_fee
return 1 - self.service_fee

def total_service_fee(self) -> Fraction:
"""Total service fee charged from rewards"""
Expand All @@ -162,7 +177,7 @@ def as_payouts(self) -> list[Transfer]:
if quote_reward_cow > 0:
result.append(
Transfer(
token=Token(COW_TOKEN_ADDRESS),
token=Token(self.reward_token_address),
recipient=self.reward_target,
amount_wei=quote_reward_cow,
)
Expand Down Expand Up @@ -195,11 +210,7 @@ def as_payouts(self) -> list[Transfer]:
result.append(
Transfer(
token=None,
recipient=(
self.reward_target
if self.bonding_pool == COW_BONDING_POOL
else self.solver
),
recipient=(self.buffer_accounting_target),
amount_wei=reimbursement_eth + total_eth_reward,
)
)
Expand All @@ -218,7 +229,7 @@ def as_payouts(self) -> list[Transfer]:
try:
result.append(
Transfer(
token=Token(COW_TOKEN_ADDRESS),
token=Token(self.reward_token_address),
recipient=self.reward_target,
amount_wei=reimbursement_cow + total_cow_reward,
)
Expand All @@ -235,11 +246,7 @@ def as_payouts(self) -> list[Transfer]:
result.append(
Transfer(
token=None,
recipient=(
self.reward_target
if self.bonding_pool == COW_BONDING_POOL
else self.solver
),
recipient=(self.buffer_accounting_target),
amount_wei=reimbursement_eth,
)
)
Expand All @@ -250,7 +257,7 @@ def as_payouts(self) -> list[Transfer]:
try:
result.append(
Transfer(
token=Token(COW_TOKEN_ADDRESS),
token=Token(self.reward_token_address),
recipient=self.reward_target,
amount_wei=total_cow_reward,
)
Expand Down Expand Up @@ -418,6 +425,25 @@ def construct_payout_dataframe(
merged_df["eth_slippage_wei"].fillna(0) + merged_df["network_fee_eth"]
)

# 5. Compute buffer accounting target
merged_df["buffer_accounting_target"] = np.where(
merged_df["pool_address"] != COW_BONDING_POOL.address,
merged_df["solver"],
merged_df["reward_target"],
)

# 6. Add reward token address
merged_df["reward_token_address"] = COW_TOKEN_ADDRESS.address

# 7. Missing service fee is treated as new solver
if any(merged_df["service_fee"].isna()):
missing_solvers = merged_df["solver"].loc[merged_df["service_fee"].isna()]
logging.warning(
f"Solvers {missing_solvers} without service fee info. Using 0%. "
f"Check service fee query."
)
merged_df["service_fee"] = merged_df["service_fee"].fillna(Fraction(0, 1)) # type: ignore

return merged_df


Expand Down Expand Up @@ -480,7 +506,8 @@ def construct_payouts(

service_fee_df = pandas.DataFrame(dune.get_service_fee_status())
service_fee_df["service_fee"] = [
datetime.strptime(time_string, "%Y-%m-%d %H:%M:%S.%f %Z") <= dune.period.start
(datetime.strptime(time_string, "%Y-%m-%d %H:%M:%S.%f %Z") <= dune.period.start)
* SERVICE_FEE_FACTOR
for time_string in service_fee_df["expires"]
]
reward_target_df = pandas.DataFrame(dune.get_vouches())
Expand Down
Loading

0 comments on commit 3645d92

Please sign in to comment.