refactor: implement phase 0 - extract pure math functions from positionHealth#35
refactor: implement phase 0 - extract pure math functions from positionHealth#35
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
- Created pure value types (RiskParams, TokenSnapshot, PositionView) - Extracted effectiveCollateral and effectiveDebt as pure functions - Refactored positionHealth to use new healthFactor pure function - Moved buildPositionView into Pool struct for proper scoping - Added parameter names for clarity in function calls This refactoring improves code modularity and testability by separating pure mathematical computations from stateful operations.
…mpatibility - Remove duplicate init functions (Cadence only allows one init per struct) - Use single parameterized initializer with explicit parameters - Update all InternalBalance() calls to pass direction and scaledBalance explicitly - All 12 tests now passing successfully
| • Mutable pieces that stay in storage | ||
| – `Pool.globalLedger[..]` indexes, `InternalPosition.balances`, queues/reserves | ||
|
|
||
| ## 2. Code (only the slice; other commands stubbed with TODO) |
There was a problem hiding this comment.
can you regenerate cadence code in this file, it shows deprecated cadence syntax, and could cause more confusion if referenced
…add phase0 pure math tests; remove DeFiBlocks dir and REFACTOR_EVALUATION.md
…gn with Phase 0 implementation
sisyphusSmiling
left a comment
There was a problem hiding this comment.
Looks like this needs to merge latest from main after #33 and update the values to use the UInt128 with 24 decimal places.
|
|
||
| // PURE HELPERS ------------------------------------------------------------- | ||
|
|
||
| access(all) fun effectiveCollateral(credit: UInt256, snap: TokenSnapshot): UInt256 { |
There was a problem hiding this comment.
I believe this and the other pure helpers can be made view. They would also benefit from comments with a brief explanation of what they do and represent in context of the protocol
| /// Copy-only representation of a position used by pure math | ||
| access(all) struct PositionView { | ||
| access(all) let balances: {Type: InternalBalance} | ||
| access(all) let tokenSnaps: {Type: TokenSnapshot} |
There was a problem hiding this comment.
np: snapshots is more intuitive IMO
| access(all) fun healthFactor(view: PositionView): UInt256 { | ||
| var coll: UInt256 = 0 | ||
| var debt: UInt256 = 0 | ||
| for t in view.balances.keys { |
There was a problem hiding this comment.
I think being a bit more verbose is helpful for comprehension, maybe using token or type instead of t so a reader doesn't have to revisit the balances type definition.
| var coll: UInt256 = 0 | ||
| var debt: UInt256 = 0 | ||
| for t in view.balances.keys { | ||
| let b = view.balances[t]! |
There was a problem hiding this comment.
Same comment as above - bal would be a bit clearer and consistent with code elsewhere in the contract
| access(all) struct RiskParams { | ||
| access(all) let collateralFactor: UInt256 | ||
| access(all) let borrowFactor: UInt256 | ||
| access(all) let liquidationBonus: UInt256 |
There was a problem hiding this comment.
Have we settled how we will go about liquidation? I thought it was going to be auction style, however this field makes me think it will be a set percentage
|
|
||
| var effColl: UInt256 = 0 | ||
| var effDebt: UInt256 = 0 | ||
| for t in view.balances.keys { |
There was a problem hiding this comment.
Similar comments as above. I think these single letter and acronym variables can be difficult to follow.
| direction: bal.direction, | ||
| scaledBalance: bal.scaledBalance | ||
| ) | ||
| let ts = self._borrowUpdatedTokenState(type: t) |
There was a problem hiding this comment.
ts could be perceived further as TokenState or TokenSnapshot - I think state would be clear and concise enough
| let bal = position.balances[t]! | ||
| balancesCopy[t] = TidalProtocol.InternalBalance( | ||
| direction: bal.direction, | ||
| scaledBalance: bal.scaledBalance | ||
| ) |
There was a problem hiding this comment.
Not sure it matters too much since we need to iterate to build snaps, but we could add a method on InternalPosition like fun copyBalances() and copy from there without the need to iteratively reconstruct the balance. Up to you but noting since I've had to use that pattern elsewhere when copying a field from a reference to a composite type.
There was a problem hiding this comment.
Perhaps this is something to add onto later, but I'd hope to see us add quite a few more test cases against these methods since part of the goal of this refactor was to increase test coverage over core calculations
…kenSnaps->snapshots; clearer var names; add InternalPosition.copyBalances; use TidalProtocolUtils.decimals in conversions
…solve conflicts; port pure helpers and PositionView types; keep origin/main positionHealth impl
…eFiActionsMathUtils; all Cadence tests passing
| access(all) fun copyBalances(): {Type: InternalBalance} { | ||
| let copy: {Type: InternalBalance} = {} | ||
| for tokenType in self.balances.keys { | ||
| let balance = self.balances[tokenType]! | ||
| copy[tokenType] = TidalProtocol.InternalBalance( | ||
| direction: balance.direction, | ||
| scaledBalance: balance.scaledBalance | ||
| ) | ||
| } | ||
| return copy | ||
| } |
There was a problem hiding this comment.
| access(all) fun copyBalances(): {Type: InternalBalance} { | |
| let copy: {Type: InternalBalance} = {} | |
| for tokenType in self.balances.keys { | |
| let balance = self.balances[tokenType]! | |
| copy[tokenType] = TidalProtocol.InternalBalance( | |
| direction: balance.direction, | |
| scaledBalance: balance.scaledBalance | |
| ) | |
| } | |
| return copy | |
| } | |
| access(all) fun copyBalances(): {Type: InternalBalance} { | |
| return self.balances | |
| } |
| risk: TidalProtocol.RiskParams( | ||
| cf: DeFiActionsMathUtils.toUInt128(self.collateralFactor[t]!), | ||
| bf: DeFiActionsMathUtils.toUInt128(self.borrowFactor[t]!), | ||
| lb: DeFiActionsMathUtils.e24 + 50_000_000_000_000_000_000_000 |
There was a problem hiding this comment.
Will we want to adjust the liquidation bonus at some point in the future? Potentially out of scope for this PR, but flagging in case we want this to be configurable
| risk: TidalProtocol.RiskParams( | ||
| cf: DeFiActionsMathUtils.toUInt128(self.collateralFactor[type]!), | ||
| bf: DeFiActionsMathUtils.toUInt128(self.borrowFactor[type]!), | ||
| lb: DeFiActionsMathUtils.e24 + 50_000_000_000_000_000_000_000 |
There was a problem hiding this comment.
Same question I made in buildPositionView regarding ability to configure liquidation bonus value. Seems like the kind of thing we'd want under pool governance.
| cf: DeFiActionsMathUtils.toUInt128(self.collateralFactor[type]!), | ||
| bf: DeFiActionsMathUtils.toUInt128(self.borrowFactor[type]!), |
There was a problem hiding this comment.
I noticed that we never actually use the UFix64 collateral or borrow factors. I think we could refactors those values to be UInt128 to avoid all the conversions whenever they're used. I suppose we could still accept them as UFix64 on addSupportedToken if we think that's less prone to input error, but perform the conversion once on write and just read the stored UInt128 value
…eturn value copy of balances
|
Acknowledged the copyBalances suggestion. Since Cadence dictionaries are values, returning self.balances gives a safe copy. I simplified InternalPosition.copyBalances() to return the balances map directly. Governance-controlled liquidationBonus and storing risk factors as UInt128 (convert once on write) are good ideas; I’ll handle both in follow-up PRs to keep Phase 0 focused. |
Overview
This PR implements Phase 0 of the refactoring plan, extracting pure mathematical functions from the
positionHealthmethod.Implementation Status
Phase 0 - All tests passing
Changes
Documentation
REFACTORING_PLAN.md: Comprehensive plan outlining the functional programming approachREFACTOR_EVALUATION.md: Evaluation criteria for the refactoringImplementation (Phase 0)
RiskParams,TokenSnapshot,PositionView) to represent immutable dataeffectiveCollateralandeffectiveDebtas pure functionspositionHealthto use newhealthFactorpure functionbuildPositionViewinto Pool struct for proper scopingBug Fixes
Benefits
Testing
✅ All 12 tests passing successfully:
The refactored code maintains the same functionality as the original implementation while following Cadence best practices.