-
Notifications
You must be signed in to change notification settings - Fork 0
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
initial refactor #1
Conversation
Can you please provide architecture documentation that explains the flow of the code.
|
fee_allocator/accounting/chains.py
Outdated
|
||
|
||
class CorePoolChain(AbstractCorePoolChain): | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the major classes, can we also document each of the inputs for init, and also maybe the ways that it is intended to be used.
This class only has 2 non-internal functions. I imagine that the intention is that it is accessed through some of the variables defined on init. These variable that have some kind of derived data are also in a sense external facing functions that should be described.
total_earned_fees_usd: Decimal = field(init=False) | ||
|
||
def __post_init__(self): | ||
self.address = self._set_address() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. As each of these vars are a result of running some logic/processing, would be good to document them and make those docs as easy to find as possible working with this object.
main.py
Outdated
input_fees = json.load(f) | ||
|
||
date_range = (ts_in_the_past, ts_now) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to take a reader step by step through the process of ingesting the data, allocating collected fees and then redisitributed based on various logic here in the main loop. I still think it could help to move away from nesting classes so deep and be a little more verbose here in main.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. So I think I got through most of the underlying stuff with some comments. Let's get this all documented/ well understood/refactored and then it should be easier to understand the logic/really debug the code.
fee_allocator/accounting/models.py
Outdated
vote_incentive_pct: Decimal | ||
|
||
|
||
class RawPoolFeeData(BaseModel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Classes like this that contain other classes/complex objects need documentation that explain their purpose and structure.
from fee_allocator.constants import POOL_OVERRIDES_URL | ||
import requests | ||
|
||
if TYPE_CHECKING: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does type checking have to do with importing pool fees? Maybe a comment about what these lines are doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TYPE_CHECKING
does static type checking, please read docs first: https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
the reason we do this instead of importing normally is because it will cause a circular import error, i guess that can be reflected as comment there
overrides_data = requests.get(POOL_OVERRIDES_URL).json() | ||
|
||
|
||
class PoolFeeOverrideMeta(ABCMeta): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these overrides are quite complex. I think it'd be good to spend some time foucsing on docuemntation here. What do all these classes/functions do? Why are they needed/how do they relate to other things? Are they setting defaults, what are the defaults/where can they be found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was implemented when it wasnt clear how much fine tuned control we wanted over the override config, but ya agree we can simplify this a lot, can probably just be a dict assuming we dont need finer control of the override allocations than what is needed now
pass | ||
|
||
|
||
class RethWethOverride(PoolFeeOverride): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I like the idea of building individual overrides as classes. It's better to have them grouped by type, and then a single json file where they can all be easily seen in config.
Overrides are config not code, there needs to be well documented code to handle the config, but the overrides themselves should remain in json files, and likely in another repo like multisig-ops with different change control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes assuming we just want the overrides to be static, all or nothing overrides like we have now, then this isnt necessary. i kept it in because i thought having the extra control would be nice if needed in the future, but might not be worth if it is making it too complex
from decimal import Decimal | ||
|
||
|
||
def return_zero_if_dust(threshold=Decimal("1E-20"), any_or_all="any"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want to do this? Is this about rounding down, we should be very very considerate of rounding logic, it should always be quite apparent what is happening. I don't think I see this being used anywhere, can it be removed? Would prefer rounding was more explicit where it happened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originally i decorated most of the PoolFee and CorePoolChain attributes/properties that returned decimals to use this to avoid having a bunch of repetitive code on each method, but ultimately chose to do the rounding at the end in the FeeAllocator. it is not used anywhere atm, but i didnt see any harm in deleting it
raise TypeError( | ||
f"`round` can only be used on methods that return `Decimal`, not {type(result)}" | ||
) | ||
return result.quantize(Decimal(10) ** -decimals) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to define a return type here. I'm confused by this function. Does it output a decimal and take a decimal. I'd actually prefer the .quantize() or other rounding operation to very visible and not buried deep in class structure.
@@ -0,0 +1,56 @@ | |||
from functools import wraps |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments below. I don't think these decorators or this style of handling rouding makes sense.
from abc import ABC | ||
|
||
""" | ||
`CorePoolChain` and `PoolFee` reference each other so they need to be abstract to prevent circular imports. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if there is a way we can do this without circural dependancies and needing to get into abstract classes. I think I understand why this is happening, but it's confusing and hard to think about and leads to more jumping around in the code when trying to debug.
Would be really good to come up with an architecture diagram or something and then think about the structure. Maybe I'llt ry to get a decent UML output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do this so that:
- core pools are aware of chain data
- chains are aware of core pool data
which is needed for doing a lot of the fee split calculations and makes it a lot cleaner when accessing data later in the FeeAllocator
methods
@jalbrekt85 can you make sure this can be closed? |
relevant parts of this pr are already included in biweekly-runs branch, good to close |
there are 3 main files to review, rest are peripheral:
overview
core_pools.py
: holds theCorePoolData
andCorePool
classCorePoolData
: organizes and holds data fetched from subgraph. the defined fields represent the parts of the core pool data that are immutable (unlikeCorePool
). this is also the data structure that gets cachedCorePool
: initialized with its respectiveCorePoolData
. defines initial incentive split to various sources (to dao, to aura, to bal, etc). these attributes are subject to change later in redistribution logic inFeeAllocator
.CorePool
also has access to its respectiveChain
class, so it can access chain wide data, like aggregate core pool fees for a chainchains.py
: holds theChain
andChains
class:Chains
: initialized withInputFees
(ex.) and a date range over which to allocate fees. initializes a list ofChain
s based on the chains/fees defined inInputFees
and contains chain agnostic data like the fee config and aura vebal shareChain
: this class contains the bulk of the core pool data collection process. uses the subgraph to create a list ofCorePoolData
and handles caching (based on chain/timestamp). also contains chain wide properties like total fees earned and can reference theChains
class for ecosystem wide datafee_allocator.py
: holds theFeeAllocator
classFeeAllocator
: initializes theChains
class and as a result, holds theCorePool
s for every chain. theredistribute_fees
method will contains the all of the redistribution logic which modifies theseCorePool
s based on the initial split. the remaining methods are for generating csvs/payloads based on the finalCorePool
dataimportant parts to review
Chain
helper methods that facilitate the core pool data collection process like_fetch_and_process_core_pool_data
and_should_add_core_pool
. review that pools are being filtered correctly and fetched data is being appropriately attributed to each pool. all of the models for the data fetched from the subgraph is defined in bal_tool's models.py which can be helpful for reviewing this partCorePool
setter methods and math. make sure the initial allocations amounts to each source are correctredistribute_fees
and_handle_aura_min
methods. lots of bulky logic that modifies core pool attributes. if the allocation numbers are ever off, its probably from these methods.setup, run and test instructions are in readme